diff --git a/README.md b/README.md
index 5e2536b4..2d574749 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,12 @@
Standalone relayer for cross-chain Avalanche Warp Message delivery.
## Usage
----
+
### Building
+
Build the relayer by running the included build script:
-```
+
+```bash
./scripts/build.sh
```
@@ -14,17 +16,23 @@ Build a Docker image by running the included build script:
```
./scripts/build-local-image.sh
```
+
### Running
+
The relayer binary accepts a path to a JSON configuration file as the sole argument. Command line configuration arguments are not currently supported.
-```
+
+```bash
./build/awm-relayer --config-file path-to-config
```
## Architecture
----
+
**Note:** The relayer in its current state supports Teleporter messages between `subnet-evm` instances. A handful of abstractions have been added to make the relayer extensible to other Warp message formats and VM types, but this work is ongoing.
+
### Components
+
The relayer consists of the following components:
+
- At the global level:
- *P2P App Network*: issues signature `AppRequests`
- *P-Chain client*: gets the validators for a subnet
@@ -34,6 +42,17 @@ The relayer consists of the following components:
- *Destination RPC client*: broadcasts transactions to the destination
### Data flow
+
+
+## Testing
+
+### Generate Mocks
+
+We use [gomock](https://pkg.go.dev/go.uber.org/mock/gomock) to generate mocks for testing. To generate mocks, run the following command at the root of the project:
+
+```bash
+go generate ./...
+```
\ No newline at end of file
diff --git a/config/config_test.go b/config/config_test.go
index 7b51f573..7c2a1a71 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -14,7 +14,6 @@ import (
"github.com/ava-labs/awm-relayer/utils"
"github.com/ethereum/go-ethereum/common"
- "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -39,7 +38,7 @@ var (
EncryptConnection: false,
MessageContracts: map[string]MessageProtocolConfig{
testAddress: {
- MessageFormat: "teleporter",
+ MessageFormat: TELEPORTER.String(),
},
},
},
@@ -62,10 +61,12 @@ var (
func TestGetDestinationRPCEndpoint(t *testing.T) {
testCases := []struct {
+ name string
s DestinationSubnet
expectedResult string
}{
{
+ name: "No encrypt connection",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
@@ -76,6 +77,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: fmt.Sprintf("http://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
+ name: "Encrypt connection",
s: DestinationSubnet{
EncryptConnection: true,
APINodeHost: "127.0.0.1",
@@ -86,6 +88,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: fmt.Sprintf("https://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
+ name: "No port",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "api.avax.network",
@@ -96,6 +99,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: fmt.Sprintf("http://api.avax.network/ext/bc/%s/rpc", testChainID),
},
{
+ name: "Primary subnet",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
@@ -106,6 +110,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: "http://127.0.0.1:9650/ext/bc/C/rpc",
},
{
+ name: "Override with set rpc endpoint",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
@@ -118,19 +123,23 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
},
}
- for i, testCase := range testCases {
- res := testCase.s.GetNodeRPCEndpoint()
- assert.Equal(t, testCase.expectedResult, res, fmt.Sprintf("test case %d failed", i))
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ res := testCase.s.GetNodeRPCEndpoint()
+ require.Equal(t, testCase.expectedResult, res)
+ })
}
}
func TestGetSourceSubnetEndpoints(t *testing.T) {
testCases := []struct {
+ name string
s SourceSubnet
expectedWsResult string
expectedRpcResult string
}{
{
+ name: "No encrypt connection",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
@@ -142,6 +151,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: fmt.Sprintf("http://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
+ name: "Encrypt connection",
s: SourceSubnet{
EncryptConnection: true,
APINodeHost: "127.0.0.1",
@@ -153,6 +163,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: fmt.Sprintf("https://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
+ name: "No port",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "api.avax.network",
@@ -164,6 +175,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: fmt.Sprintf("http://api.avax.network/ext/bc/%s/rpc", testChainID),
},
{
+ name: "Primary subnet",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
@@ -175,6 +187,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: "http://127.0.0.1:9650/ext/bc/C/rpc",
},
{
+ name: "Override with set endpoints",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
@@ -189,9 +202,11 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
},
}
- for i, testCase := range testCases {
- assert.Equal(t, testCase.expectedWsResult, testCase.s.GetNodeWSEndpoint(), fmt.Sprintf("test case %d failed", i))
- assert.Equal(t, testCase.expectedRpcResult, testCase.s.GetNodeRPCEndpoint(), fmt.Sprintf("test case %d failed", i))
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ require.Equal(t, testCase.expectedWsResult, testCase.s.GetNodeWSEndpoint())
+ require.Equal(t, testCase.expectedRpcResult, testCase.s.GetNodeRPCEndpoint())
+ })
}
}
@@ -203,11 +218,12 @@ func TestGetRelayerAccountInfo(t *testing.T) {
}
testCases := []struct {
+ name string
s DestinationSubnet
expectedResult retStruct
}{
- // valid
{
+ name: "valid",
s: DestinationSubnet{
AccountPrivateKey: "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
},
@@ -219,8 +235,8 @@ func TestGetRelayerAccountInfo(t *testing.T) {
err: nil,
},
},
- // invalid, with 0x prefix. Should be sanitized before being set in DestinationSubnet
{
+ name: "invalid 0x prefix",
s: DestinationSubnet{
AccountPrivateKey: "0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
},
@@ -232,8 +248,8 @@ func TestGetRelayerAccountInfo(t *testing.T) {
err: ErrInvalidPrivateKey,
},
},
- // invalid
{
+ name: "invalid private key",
s: DestinationSubnet{
AccountPrivateKey: "invalid56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
},
@@ -247,13 +263,15 @@ func TestGetRelayerAccountInfo(t *testing.T) {
},
}
- for i, testCase := range testCases {
- pk, addr, err := testCase.s.GetRelayerAccountInfo()
- assert.Equal(t, testCase.expectedResult.err, err, fmt.Sprintf("test case %d had unexpected error", i))
- if err == nil {
- assert.Equal(t, testCase.expectedResult.pk.D.Int64(), pk.D.Int64(), fmt.Sprintf("test case %d had mismatched pk", i))
- assert.Equal(t, testCase.expectedResult.addr, addr, fmt.Sprintf("test case %d had mismatched address", i))
- }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ pk, addr, err := testCase.s.GetRelayerAccountInfo()
+ require.Equal(t, testCase.expectedResult.err, err)
+ if err == nil {
+ require.Equal(t, testCase.expectedResult.pk.D.Int64(), pk.D.Int64())
+ require.Equal(t, testCase.expectedResult.addr, addr)
+ }
+ })
}
}
@@ -270,12 +288,11 @@ type getRelayerAccountPrivateKeyTestCase struct {
// Sets up the config file temporary environment and runs the test case.
func runGetRelayerAccountPrivateKeyTest(t *testing.T, testCase getRelayerAccountPrivateKeyTestCase) {
- require := require.New(t)
root := t.TempDir()
cfg := testCase.configModifier(testCase.baseConfig)
cfgBytes, err := json.Marshal(cfg)
- require.NoError(err)
+ require.NoError(t, err)
configFile := setupConfigJSON(t, root, string(cfgBytes))
@@ -284,13 +301,12 @@ func runGetRelayerAccountPrivateKeyTest(t *testing.T, testCase getRelayerAccount
fs := BuildFlagSet()
v, err := BuildViper(fs, flags)
- require.NoError(err)
+ require.NoError(t, err)
parsedCfg, optionOverwritten, err := BuildConfig(v)
- require.NoError(err)
- assert.Equal(t, optionOverwritten, testCase.expectedOverwritten)
- if !testCase.resultVerifier(parsedCfg) {
- t.Errorf("unexpected config.")
- }
+ require.NoError(t, err)
+ require.Equal(t, optionOverwritten, testCase.expectedOverwritten)
+
+ require.True(t, testCase.resultVerifier(parsedCfg))
}
func TestGetRelayerAccountPrivateKey_set_pk_in_config(t *testing.T) {
@@ -381,3 +397,16 @@ func setupConfigJSON(t *testing.T, rootPath string, value string) string {
require.NoError(t, os.WriteFile(configFilePath, []byte(value), 0o600))
return configFilePath
}
+
+func TestGetRelayerAccountInfoSkipChainConfigCheckCompatible(t *testing.T) {
+ accountPrivateKey := "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027"
+ expectedAddress := "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"
+
+ info := DestinationSubnet{
+ AccountPrivateKey: accountPrivateKey,
+ }
+ _, address, err := info.GetRelayerAccountInfo()
+
+ require.NoError(t, err)
+ require.Equal(t, expectedAddress, address.String())
+}
diff --git a/go.mod b/go.mod
index bbe4297d..2689cb97 100644
--- a/go.mod
+++ b/go.mod
@@ -6,11 +6,12 @@ require (
github.com/ava-labs/avalanchego v1.10.9
github.com/ava-labs/subnet-evm v0.5.4
github.com/ethereum/go-ethereum v1.12.0
- github.com/golang/mock v1.6.0
+ github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
+ go.uber.org/mock v0.2.0
go.uber.org/zap v1.25.0
)
@@ -30,13 +31,11 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
- github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
- go.uber.org/mock v0.2.0 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
diff --git a/go.sum b/go.sum
index cc461fb8..532aaf38 100644
--- a/go.sum
+++ b/go.sum
@@ -217,8 +217,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -818,7 +816,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/messages/message_manager.go b/messages/message_manager.go
index 46d608ab..00251353 100644
--- a/messages/message_manager.go
+++ b/messages/message_manager.go
@@ -1,6 +1,8 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
+//go:generate mockgen -source=$GOFILE -destination=./mocks/mock_message_manager.go -package=mocks
+
package messages
import (
diff --git a/messages/mocks/mock_message_manager.go b/messages/mocks/mock_message_manager.go
new file mode 100644
index 00000000..eba2032f
--- /dev/null
+++ b/messages/mocks/mock_message_manager.go
@@ -0,0 +1,66 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: message_manager.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+ reflect "reflect"
+
+ ids "github.com/ava-labs/avalanchego/ids"
+ warp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
+ vmtypes "github.com/ava-labs/awm-relayer/vms/vmtypes"
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockMessageManager is a mock of MessageManager interface.
+type MockMessageManager struct {
+ ctrl *gomock.Controller
+ recorder *MockMessageManagerMockRecorder
+}
+
+// MockMessageManagerMockRecorder is the mock recorder for MockMessageManager.
+type MockMessageManagerMockRecorder struct {
+ mock *MockMessageManager
+}
+
+// NewMockMessageManager creates a new mock instance.
+func NewMockMessageManager(ctrl *gomock.Controller) *MockMessageManager {
+ mock := &MockMessageManager{ctrl: ctrl}
+ mock.recorder = &MockMessageManagerMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockMessageManager) EXPECT() *MockMessageManagerMockRecorder {
+ return m.recorder
+}
+
+// SendMessage mocks base method.
+func (m *MockMessageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SendMessage", signedMessage, parsedVmPayload, destinationChainID)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// SendMessage indicates an expected call of SendMessage.
+func (mr *MockMessageManagerMockRecorder) SendMessage(signedMessage, parsedVmPayload, destinationChainID interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockMessageManager)(nil).SendMessage), signedMessage, parsedVmPayload, destinationChainID)
+}
+
+// ShouldSendMessage mocks base method.
+func (m *MockMessageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ShouldSendMessage", warpMessageInfo, destinationChainID)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ShouldSendMessage indicates an expected call of ShouldSendMessage.
+func (mr *MockMessageManagerMockRecorder) ShouldSendMessage(warpMessageInfo, destinationChainID interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldSendMessage", reflect.TypeOf((*MockMessageManager)(nil).ShouldSendMessage), warpMessageInfo, destinationChainID)
+}
diff --git a/messages/teleporter/config_test.go b/messages/teleporter/config_test.go
new file mode 100644
index 00000000..1c98a707
--- /dev/null
+++ b/messages/teleporter/config_test.go
@@ -0,0 +1,40 @@
+package teleporter
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestConfigValidate(t *testing.T) {
+ testCases := []struct {
+ name string
+ rewardAddress string
+ isError bool
+ }{
+ {
+ name: "valid",
+ rewardAddress: "0x27aE10273D17Cd7e80de8580A51f476960626e5f",
+ isError: false,
+ },
+ {
+ name: "invalid",
+ rewardAddress: "0x27aE10273D17Cd7e80de8580A51f476960626e5",
+ isError: true,
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ c := &Config{
+ RewardAddress: test.rewardAddress,
+ }
+ err := c.Validate()
+ if test.isError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
diff --git a/messages/teleporter/message.go b/messages/teleporter/message.go
index 7729b394..a2f41af0 100644
--- a/messages/teleporter/message.go
+++ b/messages/teleporter/message.go
@@ -38,7 +38,7 @@ type ReceiveCrossChainMessageInput struct {
// MessageReceivedInput is the input to messageReceived call
// in the contract deployed on the destination chain
type MessageReceivedInput struct {
- OriginChainID ids.ID `json:"relayerRewardAddress"`
+ OriginChainID ids.ID `json:"originChainID"`
MessageID *big.Int `json:"messageID"`
}
@@ -73,6 +73,10 @@ func packMessageReceivedMessage(inputStruct MessageReceivedInput) ([]byte, error
return EVMTeleporterContractABI.Pack("messageReceived", inputStruct.OriginChainID, inputStruct.MessageID)
}
+func packMessageReceivedOutput(success bool) ([]byte, error) {
+ return EVMTeleporterContractABI.PackOutput("messageReceived", success)
+}
+
func unpackMessageReceivedResult(result []byte) (bool, error) {
var success bool
err := EVMTeleporterContractABI.UnpackIntoInterface(&success, "messageReceived", result)
diff --git a/messages/teleporter/message_manager_test.go b/messages/teleporter/message_manager_test.go
new file mode 100644
index 00000000..da62f4e8
--- /dev/null
+++ b/messages/teleporter/message_manager_test.go
@@ -0,0 +1,170 @@
+// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package teleporter
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ava-labs/avalanchego/ids"
+ "github.com/ava-labs/avalanchego/utils/logging"
+ "github.com/ava-labs/avalanchego/vms/platformvm/warp"
+ "github.com/ava-labs/awm-relayer/config"
+ "github.com/ava-labs/awm-relayer/vms"
+ mock_evm "github.com/ava-labs/awm-relayer/vms/evm/mocks"
+ mock_vms "github.com/ava-labs/awm-relayer/vms/mocks"
+ "github.com/ava-labs/awm-relayer/vms/vmtypes"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/mock/gomock"
+)
+
+var (
+ messageProtocolAddress = common.HexToHash("0xd81545385803bCD83bd59f58Ba2d2c0562387F83")
+ messageProtocolConfig = config.MessageProtocolConfig{
+ MessageFormat: config.TELEPORTER.String(),
+ Settings: map[string]interface{}{
+ "reward-address": "0x27aE10273D17Cd7e80de8580A51f476960626e5f",
+ },
+ }
+ destinationChainIDString = "S4mMqUXe7vHsGiRAma6bv3CKnyaLssyAxmQ2KvFpX1KEvfFCD"
+ validRelayerAddress = common.HexToAddress("0x0123456789abcdef0123456789abcdef01234567")
+ validTeleporterMessage = TeleporterMessage{
+ MessageID: big.NewInt(1),
+ SenderAddress: common.HexToAddress("0x0123456789abcdef0123456789abcdef01234567"),
+ DestinationAddress: common.HexToAddress("0x0123456789abcdef0123456789abcdef01234567"),
+ RequiredGasLimit: big.NewInt(2),
+ AllowedRelayerAddresses: []common.Address{
+ validRelayerAddress,
+ },
+ Receipts: []TeleporterMessageReceipt{
+ {
+ ReceivedMessageID: big.NewInt(1),
+ RelayerRewardAddress: common.HexToAddress("0x0123456789abcdef0123456789abcdef01234567"),
+ },
+ },
+ Message: []byte{1, 2, 3, 4},
+ }
+)
+
+func TestShouldSendMessage(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ logger := logging.NoLog{}
+ destinationChainID, err := ids.FromString(destinationChainIDString)
+ require.NoError(t, err)
+
+ mockClient := mock_vms.NewMockDestinationClient(ctrl)
+ destinationClients := map[ids.ID]vms.DestinationClient{
+ destinationChainID: mockClient,
+ }
+
+ messageManager, err := NewMessageManager(
+ logger,
+ messageProtocolAddress,
+ messageProtocolConfig,
+ destinationClients,
+ )
+ require.NoError(t, err)
+
+ _, validMessageBytes, err := packSendCrossChainMessageEvent(common.HexToHash(destinationChainID.Hex()), validTeleporterMessage)
+ require.NoError(t, err)
+
+ messageNotDelivered, err := packMessageReceivedOutput(false)
+ require.NoError(t, err)
+
+ messageDelivered, err := packMessageReceivedOutput(true)
+ require.NoError(t, err)
+
+ warpUnsignedMessage, err := warp.NewUnsignedMessage(0, ids.Empty, validMessageBytes)
+ require.NoError(t, err)
+ testCases := []struct {
+ name string
+ destinationChainID ids.ID
+ warpMessageInfo *vmtypes.WarpMessageInfo
+ senderAddressResult common.Address
+ senderAddressTimes int
+ clientResult *mock_evm.MockClient
+ clientTimes int
+ callContractResult []byte
+ callContractTimes int
+ expectedError bool
+ expectedResult bool
+ }{
+ {
+ name: "valid message",
+ destinationChainID: destinationChainID,
+ warpMessageInfo: &vmtypes.WarpMessageInfo{
+ WarpUnsignedMessage: warpUnsignedMessage,
+ WarpPayload: validMessageBytes,
+ },
+ senderAddressResult: validRelayerAddress,
+ senderAddressTimes: 1,
+ clientResult: mock_evm.NewMockClient(ctrl),
+ clientTimes: 1,
+ callContractResult: messageNotDelivered,
+ callContractTimes: 1,
+ expectedResult: true,
+ },
+ {
+ name: "invalid message",
+ destinationChainID: destinationChainID,
+ warpMessageInfo: &vmtypes.WarpMessageInfo{
+ WarpUnsignedMessage: warpUnsignedMessage,
+ WarpPayload: []byte{1, 2, 3, 4},
+ },
+ expectedError: true,
+ },
+ {
+ name: "invalid destination chain id",
+ destinationChainID: ids.Empty,
+ warpMessageInfo: &vmtypes.WarpMessageInfo{
+ WarpUnsignedMessage: warpUnsignedMessage,
+ WarpPayload: validMessageBytes,
+ },
+ expectedError: true,
+ },
+ {
+ name: "not allowed",
+ destinationChainID: destinationChainID,
+ warpMessageInfo: &vmtypes.WarpMessageInfo{
+ WarpUnsignedMessage: warpUnsignedMessage,
+ WarpPayload: validMessageBytes,
+ },
+ senderAddressResult: common.Address{},
+ senderAddressTimes: 1,
+ expectedResult: false,
+ },
+ {
+ name: "message already delivered",
+ destinationChainID: destinationChainID,
+ warpMessageInfo: &vmtypes.WarpMessageInfo{
+ WarpUnsignedMessage: warpUnsignedMessage,
+ WarpPayload: validMessageBytes,
+ },
+ senderAddressResult: validRelayerAddress,
+ senderAddressTimes: 1,
+ clientResult: mock_evm.NewMockClient(ctrl),
+ clientTimes: 1,
+ callContractResult: messageDelivered,
+ callContractTimes: 1,
+ expectedResult: false,
+ },
+ }
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ mockClient.EXPECT().SenderAddress().Return(test.senderAddressResult).Times(test.senderAddressTimes)
+ mockClient.EXPECT().Client().Return(test.clientResult).Times(test.clientTimes)
+ if test.clientResult != nil {
+ test.clientResult.EXPECT().CallContract(gomock.Any(), gomock.Any(), gomock.Any()).Return(test.callContractResult, nil).Times(test.callContractTimes)
+ }
+ result, err := messageManager.ShouldSendMessage(test.warpMessageInfo, test.destinationChainID)
+ if test.expectedError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, test.expectedResult, result)
+ }
+ })
+ }
+}
diff --git a/messages/teleporter/message_test.go b/messages/teleporter/message_test.go
index 62740217..b17bf2c9 100644
--- a/messages/teleporter/message_test.go
+++ b/messages/teleporter/message_test.go
@@ -5,12 +5,11 @@ package teleporter
import (
"bytes"
- "encoding/hex"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func testTeleporterMessage(messageID int64) TeleporterMessage {
@@ -35,49 +34,41 @@ func testTeleporterMessage(messageID int64) TeleporterMessage {
// Pack the SendCrossChainMessage event type. PackEvent is documented as not supporting struct types, so this should be used
// with caution. Here, we only use it for testing purposes. In a real setting, the Teleporter contract should pack the event.
-func packTeleporterMessage(destinationChainID common.Hash, message TeleporterMessage) ([]byte, error) {
- _, hashes, err := EVMTeleporterContractABI.PackEvent("SendCrossChainMessage", destinationChainID, message.MessageID, message)
- return hashes, err
+func packSendCrossChainMessageEvent(destinationChainID common.Hash, message TeleporterMessage) ([]common.Hash, []byte, error) {
+ return EVMTeleporterContractABI.PackEvent("SendCrossChainMessage", destinationChainID, message.MessageID, message)
}
func TestPackUnpackTeleporterMessage(t *testing.T) {
- message := testTeleporterMessage(4)
+ var (
+ messageID int64 = 4
+ destinationChainID common.Hash = common.HexToHash("0x03")
+ )
+ message := testTeleporterMessage(messageID)
- b, err := packTeleporterMessage(common.HexToHash("0x03"), message)
- if err != nil {
- t.Errorf("failed to pack teleporter message: %v", err)
- t.FailNow()
- }
+ topics, b, err := packSendCrossChainMessageEvent(destinationChainID, message)
+ require.NoError(t, err)
+
+ // Three events where the first event topics[0] is the event signature.
+ require.Equal(t, len(topics), 3)
+ require.Equal(t, destinationChainID, topics[1])
+ require.Equal(t, new(big.Int).SetInt64(messageID), topics[2].Big())
unpacked, err := unpackTeleporterMessage(b)
- if err != nil {
- t.Errorf("failed to unpack teleporter message: %v", err)
- t.FailNow()
- }
+ require.NoError(t, err)
+
+ require.Equal(t, message.MessageID, unpacked.MessageID)
+ require.Equal(t, message.SenderAddress, unpacked.SenderAddress)
+ require.Equal(t, message.DestinationAddress, unpacked.DestinationAddress)
+ require.Equal(t, message.RequiredGasLimit, unpacked.RequiredGasLimit)
- if unpacked.MessageID.Cmp(message.MessageID) != 0 {
- t.Errorf("message ids do not match. expected: %d actual: %d", message.MessageID.Uint64(), unpacked.MessageID.Uint64())
- }
- if unpacked.SenderAddress != message.SenderAddress {
- t.Errorf("sender addresses do not match. expected: %s actual: %s", message.SenderAddress.Hex(), unpacked.SenderAddress.Hex())
- }
- if unpacked.DestinationAddress != message.DestinationAddress {
- t.Errorf("destination addresses do not match. expected: %s actual: %s", message.DestinationAddress.Hex(), unpacked.DestinationAddress.Hex())
- }
- if unpacked.RequiredGasLimit.Cmp(message.RequiredGasLimit) != 0 {
- t.Errorf("required gas limits do not match. expected: %d actual: %d", message.RequiredGasLimit.Uint64(), unpacked.RequiredGasLimit.Uint64())
- }
for i := 0; i < len(message.AllowedRelayerAddresses); i++ {
- if unpacked.AllowedRelayerAddresses[i] != message.AllowedRelayerAddresses[i] {
- t.Errorf("allowed relayer addresses %d do not match. expected: %s actual: %s", i, message.AllowedRelayerAddresses[i].Hex(), unpacked.AllowedRelayerAddresses[i].Hex())
- }
+ require.Equal(t, unpacked.AllowedRelayerAddresses[i], message.AllowedRelayerAddresses[i])
}
+
for i := 0; i < len(message.Receipts); i++ {
- assert.Equal(t, 0, unpacked.Receipts[i].ReceivedMessageID.Cmp(message.Receipts[i].ReceivedMessageID))
- assert.Equal(t, message.Receipts[i].RelayerRewardAddress, unpacked.Receipts[i].RelayerRewardAddress)
+ require.Equal(t, message.Receipts[i].ReceivedMessageID, unpacked.Receipts[i].ReceivedMessageID)
+ require.Equal(t, message.Receipts[i].RelayerRewardAddress, unpacked.Receipts[i].RelayerRewardAddress)
}
- if !bytes.Equal(unpacked.Message, message.Message) {
- t.Errorf("messages do not match. expected: %s actual: %s", hex.EncodeToString(message.Message), hex.EncodeToString(unpacked.Message))
- }
+ require.True(t, bytes.Equal(message.Message, unpacked.Message))
}
diff --git a/relayer/message_relayer.go b/relayer/message_relayer.go
index 89e066c3..a88ca365 100644
--- a/relayer/message_relayer.go
+++ b/relayer/message_relayer.go
@@ -311,7 +311,7 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e
}
// As soon as the signatures exceed the stake weight threshold we try to aggregate and send the transaction.
- if utils.CheckStakeWeightExceedsThreshold(accumulatedSignatureWeight, totalValidatorWeight) {
+ if utils.CheckStakeWeightExceedsThreshold(accumulatedSignatureWeight, totalValidatorWeight, utils.DefaultQuorumNumerator, utils.DefaultQuorumDenominator) {
aggSig, vdrBitSet, err := r.aggregateSignatures(signatureMap)
if err != nil {
r.logger.Error(
diff --git a/utils/utils.go b/utils/utils.go
index 59dc31ba..992df19f 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -26,8 +26,6 @@ const (
)
var (
- Uint256Max = (&big.Int{}).SetBytes(common.Hex2Bytes("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))
-
// Errors
ErrNilInput = errors.New("nil input")
ErrTooLarge = errors.New("exceeds uint256 maximum value")
@@ -38,18 +36,17 @@ var (
//
// CheckStakeWeightExceedsThreshold returns true if the accumulated signature weight is at least [quorumNum]/[quorumDen] of [totalWeight].
-func CheckStakeWeightExceedsThreshold(accumulatedSignatureWeight *big.Int, totalWeight uint64) bool {
+func CheckStakeWeightExceedsThreshold(accumulatedSignatureWeight *big.Int, totalWeight uint64, quorumNumerator uint64, quorumDenominator uint64) bool {
if accumulatedSignatureWeight == nil {
return false
}
// Verifies that quorumNum * totalWeight <= quorumDen * sigWeight
- scaledTotalWeight := new(big.Int).SetUint64(totalWeight)
- scaledTotalWeight.Mul(scaledTotalWeight, new(big.Int).SetUint64(DefaultQuorumNumerator))
- scaledSigWeight := new(big.Int).Mul(accumulatedSignatureWeight, new(big.Int).SetUint64(DefaultQuorumDenominator))
+ totalWeightBI := new(big.Int).SetUint64(totalWeight)
+ scaledTotalWeight := new(big.Int).Mul(totalWeightBI, new(big.Int).SetUint64(quorumNumerator))
+ scaledSigWeight := new(big.Int).Mul(accumulatedSignatureWeight, new(big.Int).SetUint64(quorumDenominator))
- thresholdMet := scaledTotalWeight.Cmp(scaledSigWeight) != 1
- return thresholdMet
+ return scaledTotalWeight.Cmp(scaledSigWeight) != 1
}
//
@@ -63,11 +60,12 @@ func BigToHashSafe(in *big.Int) (common.Hash, error) {
return common.Hash{}, ErrNilInput
}
- if in.Cmp(Uint256Max) > 0 {
+ bytes := in.Bytes()
+ if len(bytes) > common.HashLength {
return common.Hash{}, ErrTooLarge
}
- return common.BigToHash(in), nil
+ return common.BytesToHash(bytes), nil
}
func ConvertProtocol(URLString, protocol string) (string, error) {
diff --git a/utils/utils_test.go b/utils/utils_test.go
index 9b3a9156..c83a7193 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -3,40 +3,51 @@
package utils
-import "testing"
+import (
+ "math/big"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
func TestConvertProtocol(t *testing.T) {
testCases := []struct {
+ name string
urlString string
protocol string
expectedUrl string
expectedError bool
}{
{
+ name: "valid http to https",
urlString: "http://www.hello.com",
protocol: "https",
expectedUrl: "https://www.hello.com",
expectedError: false,
},
{
+ name: "valid https to http",
urlString: "https://www.hello.com",
protocol: "http",
expectedUrl: "http://www.hello.com",
expectedError: false,
},
{
+ name: "valid http to http",
urlString: "http://www.hello.com",
protocol: "http",
expectedUrl: "http://www.hello.com",
expectedError: false,
},
{
+ name: "valid https to https",
urlString: "https://www.hello.com",
protocol: "https",
expectedUrl: "https://www.hello.com",
expectedError: false,
},
{
+ name: "invalid protocol",
urlString: "http://www.hello.com",
protocol: "\n",
expectedUrl: "",
@@ -44,45 +55,104 @@ func TestConvertProtocol(t *testing.T) {
},
}
- for i, testCase := range testCases {
- actualUrl, err := ConvertProtocol(testCase.urlString, testCase.protocol)
- if err != nil && !testCase.expectedError {
- t.Errorf("test case %d failed with unexpected error", i)
- }
- if err == nil && testCase.expectedError {
- t.Errorf("test case %d did not produce expected error", i)
- }
- if actualUrl != testCase.expectedUrl {
- t.Errorf("test case %d had unexpected URL. Actual: %s, Expected: %s", i, actualUrl, testCase.expectedUrl)
- }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ actualUrl, err := ConvertProtocol(testCase.urlString, testCase.protocol)
+
+ if testCase.expectedError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, testCase.expectedUrl, actualUrl)
+ }
+ })
}
}
func TestSanitizeHashString(t *testing.T) {
testCases := []struct {
+ name string
hash string
expectedResult string
}{
- // Remove leading 0x from hex string
{
+ name: "remove leading 0x",
hash: "0x1234",
expectedResult: "1234",
},
- // Return original hex string
{
+ name: "return original non leading 0x",
hash: "1234",
expectedResult: "1234",
},
- // Return original string, leading 0x is not hex
{
+ name: "return original length not divisible by 2",
hash: "0x1234g",
expectedResult: "0x1234g",
},
}
- for i, testCase := range testCases {
- actualResult := SanitizeHashString(testCase.hash)
- if actualResult != testCase.expectedResult {
- t.Errorf("test case %d had unexpected result. Actual: %s, Expected: %s", i, actualResult, testCase.expectedResult)
- }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ actualResult := SanitizeHashString(testCase.hash)
+ require.Equal(t, testCase.expectedResult, actualResult)
+ })
+ }
+}
+
+func TestCheckStakeWeightExceedsThreshold(t *testing.T) {
+ testCases := []struct {
+ name string
+ accumulatedSignatureWeight uint64
+ totalWeight uint64
+ quorumNumerator uint64
+ quorumDenominator uint64
+ expectedResult bool
+ }{
+ {
+ name: "zero case",
+ accumulatedSignatureWeight: 0,
+ totalWeight: 0,
+ quorumNumerator: 0,
+ quorumDenominator: 0,
+ expectedResult: true,
+ },
+ {
+ name: "valid case",
+ accumulatedSignatureWeight: 67_000_000,
+ totalWeight: 100_000_000,
+ quorumNumerator: 67,
+ quorumDenominator: 100,
+ expectedResult: true,
+ },
+ {
+ name: "invalid case",
+ accumulatedSignatureWeight: 66_999_999,
+ totalWeight: 100_000_000,
+ quorumNumerator: 67,
+ quorumDenominator: 100,
+ expectedResult: false,
+ },
+ {
+ name: "valid 100 percent case",
+ accumulatedSignatureWeight: 67_000_000,
+ totalWeight: 67_000_000,
+ quorumNumerator: 100,
+ quorumDenominator: 100,
+ expectedResult: true,
+ },
+ {
+ name: "invalid 100 percent case",
+ accumulatedSignatureWeight: 66_999_999,
+ totalWeight: 67_000_000,
+ quorumNumerator: 100,
+ quorumDenominator: 100,
+ expectedResult: false,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ actualResult := CheckStakeWeightExceedsThreshold(new(big.Int).SetUint64(testCase.accumulatedSignatureWeight), testCase.totalWeight, testCase.quorumNumerator, testCase.quorumDenominator)
+ require.Equal(t, testCase.expectedResult, actualResult)
+ })
}
}
diff --git a/vms/destination_client.go b/vms/destination_client.go
index 44752d0f..c7520607 100644
--- a/vms/destination_client.go
+++ b/vms/destination_client.go
@@ -1,6 +1,8 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
+//go:generate mockgen -source=$GOFILE -destination=./mocks/mock_destination_client.go -package=mocks
+
package vms
import (
@@ -44,8 +46,8 @@ func NewDestinationClient(logger logging.Logger, subnetInfo config.DestinationSu
// CreateDestinationClients creates destination clients for all subnets configured as destinations
func CreateDestinationClients(logger logging.Logger, relayerConfig config.Config) (map[ids.ID]DestinationClient, error) {
destinationClients := make(map[ids.ID]DestinationClient)
- for _, s := range relayerConfig.DestinationSubnets {
- chainID, err := ids.FromString(s.ChainID)
+ for _, subnetInfo := range relayerConfig.DestinationSubnets {
+ chainID, err := ids.FromString(subnetInfo.ChainID)
if err != nil {
logger.Error(
"Failed to decode base-58 encoded source chain ID",
@@ -61,7 +63,7 @@ func CreateDestinationClients(logger logging.Logger, relayerConfig config.Config
continue
}
- destinationClient, err := NewDestinationClient(logger, s)
+ destinationClient, err := NewDestinationClient(logger, subnetInfo)
if err != nil {
logger.Error(
"Could not create destination client",
diff --git a/vms/evm/contract_message_test.go b/vms/evm/contract_message_test.go
index 96da6191..fbc50c91 100644
--- a/vms/evm/contract_message_test.go
+++ b/vms/evm/contract_message_test.go
@@ -13,7 +13,7 @@ import (
"github.com/ava-labs/awm-relayer/config"
warpPayload "github.com/ava-labs/subnet-evm/warp/payload"
"github.com/ethereum/go-ethereum/common"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
@@ -47,30 +47,44 @@ func createUnsignedMessage() *warp.UnsignedMessage {
}
func TestUnpack(t *testing.T) {
- ctrl := gomock.NewController(t)
-
- m := NewContractMessage(logging.NewMockLogger(ctrl), config.SourceSubnet{})
+ mockLogger := logging.NewMockLogger(gomock.NewController(t))
+ m := NewContractMessage(mockLogger, config.SourceSubnet{})
testCases := []struct {
- input string
- networkID uint32
+ name string
+ input string
+ networkID uint32
+ errorLogTimes int
+ expectError bool
}{
{
- input: "0000000000007fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d50000005200000000000027ae10273d17cd7e80de8580a51f476960626e5f0000000000000000000000000000000000000000000000000000000000000000123412341234123412341234123412341234123400000000",
- networkID: 0,
+ name: "valid",
+ input: "0000000000007fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d50000005200000000000027ae10273d17cd7e80de8580a51f476960626e5f0000000000000000000000000000000000000000000000000000000000000000123412341234123412341234123412341234123400000000",
+ networkID: 0,
+ errorLogTimes: 0,
+ expectError: false,
+ },
+ {
+ name: "invalid",
+ errorLogTimes: 1,
+ input: "1000000000007fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d50000005200000000000027ae10273d17cd7e80de8580a51f476960626e5f0000000000000000000000000000000000000000000000000000000000000000123412341234123412341234123412341234123400000000",
+ expectError: true,
},
}
for _, testCase := range testCases {
- input, err := hex.DecodeString(testCase.input)
- if err != nil {
- t.Errorf("failed to decode test input: %v", err)
- }
- msg, err := m.UnpackWarpMessage(input)
- if err != nil {
- t.Errorf("failed to unpack message: %v", err)
- }
+ t.Run(testCase.name, func(t *testing.T) {
+ input, err := hex.DecodeString(testCase.input)
+ require.NoError(t, err)
- assert.Equal(t, testCase.networkID, msg.WarpUnsignedMessage.NetworkID)
+ mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).Times(testCase.errorLogTimes)
+ msg, err := m.UnpackWarpMessage(input)
+ if testCase.expectError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, testCase.networkID, msg.WarpUnsignedMessage.NetworkID)
+ }
+ })
}
}
diff --git a/vms/evm/destination_client.go b/vms/evm/destination_client.go
index b904e54d..ea4fcd3a 100644
--- a/vms/evm/destination_client.go
+++ b/vms/evm/destination_client.go
@@ -1,6 +1,8 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
+//go:generate mockgen -source=$GOFILE -destination=./mocks/mock_eth_client.go -package=mocks
+
package evm
import (
@@ -29,6 +31,11 @@ const (
MaxPriorityFeePerGas = 2500000000 // 2.5 gwei
)
+// Client interface wraps the ethclient.Client interface for mocking purposes.
+type Client interface {
+ ethclient.Client
+}
+
// Implements DestinationClient
type destinationClient struct {
client ethclient.Client
diff --git a/vms/evm/destination_client_test.go b/vms/evm/destination_client_test.go
new file mode 100644
index 00000000..b0315e5d
--- /dev/null
+++ b/vms/evm/destination_client_test.go
@@ -0,0 +1,117 @@
+// (c) 2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "testing"
+
+ "github.com/ava-labs/avalanchego/utils/logging"
+ avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
+ "github.com/ava-labs/awm-relayer/config"
+ mock_ethclient "github.com/ava-labs/awm-relayer/vms/evm/mocks"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/mock/gomock"
+)
+
+var destinationSubnet = config.DestinationSubnet{
+ SubnetID: "2TGBXcnwx5PqiXWiqxAKUaNSqDguXNh1mxnp82jui68hxJSZAx",
+ ChainID: "S4mMqUXe7vHsGiRAma6bv3CKnyaLssyAxmQ2KvFpX1KEvfFCD",
+ VM: config.EVM.String(),
+ APINodeHost: "127.0.0.1",
+ APINodePort: 9650,
+ EncryptConnection: false,
+ RPCEndpoint: "https://subnets.avax.network/mysubnet/rpc",
+ AccountPrivateKey: "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
+}
+
+func TestSendTx(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ mockClient := mock_ethclient.NewMockClient(ctrl)
+ pk, eoa, err := destinationSubnet.GetRelayerAccountInfo()
+ require.NoError(t, err)
+
+ destinationClient := &destinationClient{
+ lock: &sync.Mutex{},
+ logger: logging.NoLog{},
+ client: mockClient,
+ pk: pk,
+ eoa: eoa,
+ }
+
+ testError := fmt.Errorf("call errored")
+ testCases := []struct {
+ name string
+ chainIDErr error
+ chainIDTimes int
+ estimateBaseFeeErr error
+ estimateBaseFeeTimes int
+ suggestGasTipCapErr error
+ suggestGasTipCapTimes int
+ sendTransactionErr error
+ sendTransactionTimes int
+ expectError bool
+ }{
+ {
+ name: "valid",
+ chainIDTimes: 1,
+ estimateBaseFeeTimes: 1,
+ suggestGasTipCapTimes: 1,
+ sendTransactionTimes: 1,
+ },
+ {
+ name: "invalid chainID",
+ chainIDErr: testError,
+ chainIDTimes: 1,
+ expectError: true,
+ },
+ {
+ name: "invalid estimateBaseFee",
+ chainIDTimes: 1,
+ estimateBaseFeeErr: testError,
+ estimateBaseFeeTimes: 1,
+ expectError: true,
+ },
+ {
+ name: "invalid suggestGasTipCap",
+ chainIDTimes: 1,
+ estimateBaseFeeTimes: 1,
+ suggestGasTipCapErr: testError,
+ suggestGasTipCapTimes: 1,
+ expectError: true,
+ },
+ {
+ name: "invalid sendTransaction",
+ chainIDTimes: 1,
+ estimateBaseFeeTimes: 1,
+ suggestGasTipCapTimes: 1,
+ sendTransactionErr: testError,
+ sendTransactionTimes: 1,
+ expectError: true,
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ warpMsg := &avalancheWarp.Message{}
+ toAddress := "0x27aE10273D17Cd7e80de8580A51f476960626e5f"
+
+ gomock.InOrder(
+ mockClient.EXPECT().ChainID(gomock.Any()).Return(new(big.Int), test.chainIDErr).Times(test.chainIDTimes),
+ mockClient.EXPECT().EstimateBaseFee(gomock.Any()).Return(new(big.Int), test.estimateBaseFeeErr).Times(test.estimateBaseFeeTimes),
+ mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).Return(new(big.Int), test.suggestGasTipCapErr).Times(test.suggestGasTipCapTimes),
+ mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).Return(test.sendTransactionErr).Times(test.sendTransactionTimes),
+ )
+
+ err := destinationClient.SendTx(warpMsg, toAddress, 0, []byte{})
+ if test.expectError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
diff --git a/vms/evm/mocks/mock_eth_client.go b/vms/evm/mocks/mock_eth_client.go
new file mode 100644
index 00000000..92fbf1a6
--- /dev/null
+++ b/vms/evm/mocks/mock_eth_client.go
@@ -0,0 +1,561 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: destination_client.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+ context "context"
+ big "math/big"
+ reflect "reflect"
+
+ ids "github.com/ava-labs/avalanchego/ids"
+ types "github.com/ava-labs/subnet-evm/core/types"
+ interfaces "github.com/ava-labs/subnet-evm/interfaces"
+ common "github.com/ethereum/go-ethereum/common"
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockClient is a mock of Client interface.
+type MockClient struct {
+ ctrl *gomock.Controller
+ recorder *MockClientMockRecorder
+}
+
+// MockClientMockRecorder is the mock recorder for MockClient.
+type MockClientMockRecorder struct {
+ mock *MockClient
+}
+
+// NewMockClient creates a new mock instance.
+func NewMockClient(ctrl *gomock.Controller) *MockClient {
+ mock := &MockClient{ctrl: ctrl}
+ mock.recorder = &MockClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockClient) EXPECT() *MockClientMockRecorder {
+ return m.recorder
+}
+
+// AcceptedCallContract mocks base method.
+func (m *MockClient) AcceptedCallContract(arg0 context.Context, arg1 interfaces.CallMsg) ([]byte, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "AcceptedCallContract", arg0, arg1)
+ ret0, _ := ret[0].([]byte)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// AcceptedCallContract indicates an expected call of AcceptedCallContract.
+func (mr *MockClientMockRecorder) AcceptedCallContract(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptedCallContract", reflect.TypeOf((*MockClient)(nil).AcceptedCallContract), arg0, arg1)
+}
+
+// AcceptedCodeAt mocks base method.
+func (m *MockClient) AcceptedCodeAt(arg0 context.Context, arg1 common.Address) ([]byte, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "AcceptedCodeAt", arg0, arg1)
+ ret0, _ := ret[0].([]byte)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// AcceptedCodeAt indicates an expected call of AcceptedCodeAt.
+func (mr *MockClientMockRecorder) AcceptedCodeAt(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptedCodeAt", reflect.TypeOf((*MockClient)(nil).AcceptedCodeAt), arg0, arg1)
+}
+
+// AcceptedNonceAt mocks base method.
+func (m *MockClient) AcceptedNonceAt(arg0 context.Context, arg1 common.Address) (uint64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "AcceptedNonceAt", arg0, arg1)
+ ret0, _ := ret[0].(uint64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// AcceptedNonceAt indicates an expected call of AcceptedNonceAt.
+func (mr *MockClientMockRecorder) AcceptedNonceAt(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptedNonceAt", reflect.TypeOf((*MockClient)(nil).AcceptedNonceAt), arg0, arg1)
+}
+
+// AssetBalanceAt mocks base method.
+func (m *MockClient) AssetBalanceAt(arg0 context.Context, arg1 common.Address, arg2 ids.ID, arg3 *big.Int) (*big.Int, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "AssetBalanceAt", arg0, arg1, arg2, arg3)
+ ret0, _ := ret[0].(*big.Int)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// AssetBalanceAt indicates an expected call of AssetBalanceAt.
+func (mr *MockClientMockRecorder) AssetBalanceAt(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssetBalanceAt", reflect.TypeOf((*MockClient)(nil).AssetBalanceAt), arg0, arg1, arg2, arg3)
+}
+
+// BalanceAt mocks base method.
+func (m *MockClient) BalanceAt(arg0 context.Context, arg1 common.Address, arg2 *big.Int) (*big.Int, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "BalanceAt", arg0, arg1, arg2)
+ ret0, _ := ret[0].(*big.Int)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// BalanceAt indicates an expected call of BalanceAt.
+func (mr *MockClientMockRecorder) BalanceAt(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BalanceAt", reflect.TypeOf((*MockClient)(nil).BalanceAt), arg0, arg1, arg2)
+}
+
+// BlockByHash mocks base method.
+func (m *MockClient) BlockByHash(arg0 context.Context, arg1 common.Hash) (*types.Block, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "BlockByHash", arg0, arg1)
+ ret0, _ := ret[0].(*types.Block)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// BlockByHash indicates an expected call of BlockByHash.
+func (mr *MockClientMockRecorder) BlockByHash(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByHash", reflect.TypeOf((*MockClient)(nil).BlockByHash), arg0, arg1)
+}
+
+// BlockByNumber mocks base method.
+func (m *MockClient) BlockByNumber(arg0 context.Context, arg1 *big.Int) (*types.Block, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "BlockByNumber", arg0, arg1)
+ ret0, _ := ret[0].(*types.Block)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// BlockByNumber indicates an expected call of BlockByNumber.
+func (mr *MockClientMockRecorder) BlockByNumber(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByNumber", reflect.TypeOf((*MockClient)(nil).BlockByNumber), arg0, arg1)
+}
+
+// BlockNumber mocks base method.
+func (m *MockClient) BlockNumber(arg0 context.Context) (uint64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "BlockNumber", arg0)
+ ret0, _ := ret[0].(uint64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// BlockNumber indicates an expected call of BlockNumber.
+func (mr *MockClientMockRecorder) BlockNumber(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockNumber", reflect.TypeOf((*MockClient)(nil).BlockNumber), arg0)
+}
+
+// CallContract mocks base method.
+func (m *MockClient) CallContract(arg0 context.Context, arg1 interfaces.CallMsg, arg2 *big.Int) ([]byte, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CallContract", arg0, arg1, arg2)
+ ret0, _ := ret[0].([]byte)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CallContract indicates an expected call of CallContract.
+func (mr *MockClientMockRecorder) CallContract(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallContract", reflect.TypeOf((*MockClient)(nil).CallContract), arg0, arg1, arg2)
+}
+
+// CallContractAtHash mocks base method.
+func (m *MockClient) CallContractAtHash(ctx context.Context, msg interfaces.CallMsg, blockHash common.Hash) ([]byte, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CallContractAtHash", ctx, msg, blockHash)
+ ret0, _ := ret[0].([]byte)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CallContractAtHash indicates an expected call of CallContractAtHash.
+func (mr *MockClientMockRecorder) CallContractAtHash(ctx, msg, blockHash interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallContractAtHash", reflect.TypeOf((*MockClient)(nil).CallContractAtHash), ctx, msg, blockHash)
+}
+
+// ChainID mocks base method.
+func (m *MockClient) ChainID(arg0 context.Context) (*big.Int, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ChainID", arg0)
+ ret0, _ := ret[0].(*big.Int)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ChainID indicates an expected call of ChainID.
+func (mr *MockClientMockRecorder) ChainID(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainID", reflect.TypeOf((*MockClient)(nil).ChainID), arg0)
+}
+
+// Close mocks base method.
+func (m *MockClient) Close() {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Close")
+}
+
+// Close indicates an expected call of Close.
+func (mr *MockClientMockRecorder) Close() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockClient)(nil).Close))
+}
+
+// CodeAt mocks base method.
+func (m *MockClient) CodeAt(arg0 context.Context, arg1 common.Address, arg2 *big.Int) ([]byte, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CodeAt", arg0, arg1, arg2)
+ ret0, _ := ret[0].([]byte)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CodeAt indicates an expected call of CodeAt.
+func (mr *MockClientMockRecorder) CodeAt(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CodeAt", reflect.TypeOf((*MockClient)(nil).CodeAt), arg0, arg1, arg2)
+}
+
+// EstimateBaseFee mocks base method.
+func (m *MockClient) EstimateBaseFee(arg0 context.Context) (*big.Int, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "EstimateBaseFee", arg0)
+ ret0, _ := ret[0].(*big.Int)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// EstimateBaseFee indicates an expected call of EstimateBaseFee.
+func (mr *MockClientMockRecorder) EstimateBaseFee(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateBaseFee", reflect.TypeOf((*MockClient)(nil).EstimateBaseFee), arg0)
+}
+
+// EstimateGas mocks base method.
+func (m *MockClient) EstimateGas(arg0 context.Context, arg1 interfaces.CallMsg) (uint64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "EstimateGas", arg0, arg1)
+ ret0, _ := ret[0].(uint64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// EstimateGas indicates an expected call of EstimateGas.
+func (mr *MockClientMockRecorder) EstimateGas(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGas", reflect.TypeOf((*MockClient)(nil).EstimateGas), arg0, arg1)
+}
+
+// FeeHistory mocks base method.
+func (m *MockClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*interfaces.FeeHistory, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "FeeHistory", ctx, blockCount, lastBlock, rewardPercentiles)
+ ret0, _ := ret[0].(*interfaces.FeeHistory)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// FeeHistory indicates an expected call of FeeHistory.
+func (mr *MockClientMockRecorder) FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FeeHistory", reflect.TypeOf((*MockClient)(nil).FeeHistory), ctx, blockCount, lastBlock, rewardPercentiles)
+}
+
+// FilterLogs mocks base method.
+func (m *MockClient) FilterLogs(arg0 context.Context, arg1 interfaces.FilterQuery) ([]types.Log, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "FilterLogs", arg0, arg1)
+ ret0, _ := ret[0].([]types.Log)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// FilterLogs indicates an expected call of FilterLogs.
+func (mr *MockClientMockRecorder) FilterLogs(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterLogs", reflect.TypeOf((*MockClient)(nil).FilterLogs), arg0, arg1)
+}
+
+// HeaderByHash mocks base method.
+func (m *MockClient) HeaderByHash(arg0 context.Context, arg1 common.Hash) (*types.Header, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "HeaderByHash", arg0, arg1)
+ ret0, _ := ret[0].(*types.Header)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// HeaderByHash indicates an expected call of HeaderByHash.
+func (mr *MockClientMockRecorder) HeaderByHash(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeaderByHash", reflect.TypeOf((*MockClient)(nil).HeaderByHash), arg0, arg1)
+}
+
+// HeaderByNumber mocks base method.
+func (m *MockClient) HeaderByNumber(arg0 context.Context, arg1 *big.Int) (*types.Header, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "HeaderByNumber", arg0, arg1)
+ ret0, _ := ret[0].(*types.Header)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// HeaderByNumber indicates an expected call of HeaderByNumber.
+func (mr *MockClientMockRecorder) HeaderByNumber(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeaderByNumber", reflect.TypeOf((*MockClient)(nil).HeaderByNumber), arg0, arg1)
+}
+
+// NetworkID mocks base method.
+func (m *MockClient) NetworkID(arg0 context.Context) (*big.Int, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "NetworkID", arg0)
+ ret0, _ := ret[0].(*big.Int)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// NetworkID indicates an expected call of NetworkID.
+func (mr *MockClientMockRecorder) NetworkID(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetworkID", reflect.TypeOf((*MockClient)(nil).NetworkID), arg0)
+}
+
+// NonceAt mocks base method.
+func (m *MockClient) NonceAt(arg0 context.Context, arg1 common.Address, arg2 *big.Int) (uint64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "NonceAt", arg0, arg1, arg2)
+ ret0, _ := ret[0].(uint64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// NonceAt indicates an expected call of NonceAt.
+func (mr *MockClientMockRecorder) NonceAt(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NonceAt", reflect.TypeOf((*MockClient)(nil).NonceAt), arg0, arg1, arg2)
+}
+
+// SendTransaction mocks base method.
+func (m *MockClient) SendTransaction(arg0 context.Context, arg1 *types.Transaction) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SendTransaction", arg0, arg1)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// SendTransaction indicates an expected call of SendTransaction.
+func (mr *MockClientMockRecorder) SendTransaction(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransaction", reflect.TypeOf((*MockClient)(nil).SendTransaction), arg0, arg1)
+}
+
+// StorageAt mocks base method.
+func (m *MockClient) StorageAt(arg0 context.Context, arg1 common.Address, arg2 common.Hash, arg3 *big.Int) ([]byte, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "StorageAt", arg0, arg1, arg2, arg3)
+ ret0, _ := ret[0].([]byte)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// StorageAt indicates an expected call of StorageAt.
+func (mr *MockClientMockRecorder) StorageAt(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorageAt", reflect.TypeOf((*MockClient)(nil).StorageAt), arg0, arg1, arg2, arg3)
+}
+
+// SubscribeFilterLogs mocks base method.
+func (m *MockClient) SubscribeFilterLogs(arg0 context.Context, arg1 interfaces.FilterQuery, arg2 chan<- types.Log) (interfaces.Subscription, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SubscribeFilterLogs", arg0, arg1, arg2)
+ ret0, _ := ret[0].(interfaces.Subscription)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SubscribeFilterLogs indicates an expected call of SubscribeFilterLogs.
+func (mr *MockClientMockRecorder) SubscribeFilterLogs(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeFilterLogs", reflect.TypeOf((*MockClient)(nil).SubscribeFilterLogs), arg0, arg1, arg2)
+}
+
+// SubscribeNewAcceptedTransactions mocks base method.
+func (m *MockClient) SubscribeNewAcceptedTransactions(arg0 context.Context, arg1 chan<- *common.Hash) (interfaces.Subscription, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SubscribeNewAcceptedTransactions", arg0, arg1)
+ ret0, _ := ret[0].(interfaces.Subscription)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SubscribeNewAcceptedTransactions indicates an expected call of SubscribeNewAcceptedTransactions.
+func (mr *MockClientMockRecorder) SubscribeNewAcceptedTransactions(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeNewAcceptedTransactions", reflect.TypeOf((*MockClient)(nil).SubscribeNewAcceptedTransactions), arg0, arg1)
+}
+
+// SubscribeNewHead mocks base method.
+func (m *MockClient) SubscribeNewHead(arg0 context.Context, arg1 chan<- *types.Header) (interfaces.Subscription, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SubscribeNewHead", arg0, arg1)
+ ret0, _ := ret[0].(interfaces.Subscription)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SubscribeNewHead indicates an expected call of SubscribeNewHead.
+func (mr *MockClientMockRecorder) SubscribeNewHead(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeNewHead", reflect.TypeOf((*MockClient)(nil).SubscribeNewHead), arg0, arg1)
+}
+
+// SubscribeNewPendingTransactions mocks base method.
+func (m *MockClient) SubscribeNewPendingTransactions(arg0 context.Context, arg1 chan<- *common.Hash) (interfaces.Subscription, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SubscribeNewPendingTransactions", arg0, arg1)
+ ret0, _ := ret[0].(interfaces.Subscription)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SubscribeNewPendingTransactions indicates an expected call of SubscribeNewPendingTransactions.
+func (mr *MockClientMockRecorder) SubscribeNewPendingTransactions(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeNewPendingTransactions", reflect.TypeOf((*MockClient)(nil).SubscribeNewPendingTransactions), arg0, arg1)
+}
+
+// SuggestGasPrice mocks base method.
+func (m *MockClient) SuggestGasPrice(arg0 context.Context) (*big.Int, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SuggestGasPrice", arg0)
+ ret0, _ := ret[0].(*big.Int)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SuggestGasPrice indicates an expected call of SuggestGasPrice.
+func (mr *MockClientMockRecorder) SuggestGasPrice(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SuggestGasPrice", reflect.TypeOf((*MockClient)(nil).SuggestGasPrice), arg0)
+}
+
+// SuggestGasTipCap mocks base method.
+func (m *MockClient) SuggestGasTipCap(arg0 context.Context) (*big.Int, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SuggestGasTipCap", arg0)
+ ret0, _ := ret[0].(*big.Int)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SuggestGasTipCap indicates an expected call of SuggestGasTipCap.
+func (mr *MockClientMockRecorder) SuggestGasTipCap(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SuggestGasTipCap", reflect.TypeOf((*MockClient)(nil).SuggestGasTipCap), arg0)
+}
+
+// SyncProgress mocks base method.
+func (m *MockClient) SyncProgress(ctx context.Context) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SyncProgress", ctx)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// SyncProgress indicates an expected call of SyncProgress.
+func (mr *MockClientMockRecorder) SyncProgress(ctx interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncProgress", reflect.TypeOf((*MockClient)(nil).SyncProgress), ctx)
+}
+
+// TransactionByHash mocks base method.
+func (m *MockClient) TransactionByHash(arg0 context.Context, arg1 common.Hash) (*types.Transaction, bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "TransactionByHash", arg0, arg1)
+ ret0, _ := ret[0].(*types.Transaction)
+ ret1, _ := ret[1].(bool)
+ ret2, _ := ret[2].(error)
+ return ret0, ret1, ret2
+}
+
+// TransactionByHash indicates an expected call of TransactionByHash.
+func (mr *MockClientMockRecorder) TransactionByHash(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionByHash", reflect.TypeOf((*MockClient)(nil).TransactionByHash), arg0, arg1)
+}
+
+// TransactionCount mocks base method.
+func (m *MockClient) TransactionCount(arg0 context.Context, arg1 common.Hash) (uint, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "TransactionCount", arg0, arg1)
+ ret0, _ := ret[0].(uint)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// TransactionCount indicates an expected call of TransactionCount.
+func (mr *MockClientMockRecorder) TransactionCount(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionCount", reflect.TypeOf((*MockClient)(nil).TransactionCount), arg0, arg1)
+}
+
+// TransactionInBlock mocks base method.
+func (m *MockClient) TransactionInBlock(arg0 context.Context, arg1 common.Hash, arg2 uint) (*types.Transaction, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "TransactionInBlock", arg0, arg1, arg2)
+ ret0, _ := ret[0].(*types.Transaction)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// TransactionInBlock indicates an expected call of TransactionInBlock.
+func (mr *MockClientMockRecorder) TransactionInBlock(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionInBlock", reflect.TypeOf((*MockClient)(nil).TransactionInBlock), arg0, arg1, arg2)
+}
+
+// TransactionReceipt mocks base method.
+func (m *MockClient) TransactionReceipt(arg0 context.Context, arg1 common.Hash) (*types.Receipt, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "TransactionReceipt", arg0, arg1)
+ ret0, _ := ret[0].(*types.Receipt)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// TransactionReceipt indicates an expected call of TransactionReceipt.
+func (mr *MockClientMockRecorder) TransactionReceipt(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionReceipt", reflect.TypeOf((*MockClient)(nil).TransactionReceipt), arg0, arg1)
+}
+
+// TransactionSender mocks base method.
+func (m *MockClient) TransactionSender(arg0 context.Context, arg1 *types.Transaction, arg2 common.Hash, arg3 uint) (common.Address, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "TransactionSender", arg0, arg1, arg2, arg3)
+ ret0, _ := ret[0].(common.Address)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// TransactionSender indicates an expected call of TransactionSender.
+func (mr *MockClientMockRecorder) TransactionSender(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionSender", reflect.TypeOf((*MockClient)(nil).TransactionSender), arg0, arg1, arg2, arg3)
+}
diff --git a/vms/mocks/mock_destination_client.go b/vms/mocks/mock_destination_client.go
new file mode 100644
index 00000000..b612cfe7
--- /dev/null
+++ b/vms/mocks/mock_destination_client.go
@@ -0,0 +1,93 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: destination_client.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+ reflect "reflect"
+
+ ids "github.com/ava-labs/avalanchego/ids"
+ warp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
+ common "github.com/ethereum/go-ethereum/common"
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockDestinationClient is a mock of DestinationClient interface.
+type MockDestinationClient struct {
+ ctrl *gomock.Controller
+ recorder *MockDestinationClientMockRecorder
+}
+
+// MockDestinationClientMockRecorder is the mock recorder for MockDestinationClient.
+type MockDestinationClientMockRecorder struct {
+ mock *MockDestinationClient
+}
+
+// NewMockDestinationClient creates a new mock instance.
+func NewMockDestinationClient(ctrl *gomock.Controller) *MockDestinationClient {
+ mock := &MockDestinationClient{ctrl: ctrl}
+ mock.recorder = &MockDestinationClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockDestinationClient) EXPECT() *MockDestinationClientMockRecorder {
+ return m.recorder
+}
+
+// Client mocks base method.
+func (m *MockDestinationClient) Client() interface{} {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Client")
+ ret0, _ := ret[0].(interface{})
+ return ret0
+}
+
+// Client indicates an expected call of Client.
+func (mr *MockDestinationClientMockRecorder) Client() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockDestinationClient)(nil).Client))
+}
+
+// DestinationChainID mocks base method.
+func (m *MockDestinationClient) DestinationChainID() ids.ID {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DestinationChainID")
+ ret0, _ := ret[0].(ids.ID)
+ return ret0
+}
+
+// DestinationChainID indicates an expected call of DestinationChainID.
+func (mr *MockDestinationClientMockRecorder) DestinationChainID() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DestinationChainID", reflect.TypeOf((*MockDestinationClient)(nil).DestinationChainID))
+}
+
+// SendTx mocks base method.
+func (m *MockDestinationClient) SendTx(signedMessage *warp.Message, toAddress string, gasLimit uint64, callData []byte) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SendTx", signedMessage, toAddress, gasLimit, callData)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// SendTx indicates an expected call of SendTx.
+func (mr *MockDestinationClientMockRecorder) SendTx(signedMessage, toAddress, gasLimit, callData interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTx", reflect.TypeOf((*MockDestinationClient)(nil).SendTx), signedMessage, toAddress, gasLimit, callData)
+}
+
+// SenderAddress mocks base method.
+func (m *MockDestinationClient) SenderAddress() common.Address {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SenderAddress")
+ ret0, _ := ret[0].(common.Address)
+ return ret0
+}
+
+// SenderAddress indicates an expected call of SenderAddress.
+func (mr *MockDestinationClientMockRecorder) SenderAddress() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SenderAddress", reflect.TypeOf((*MockDestinationClient)(nil).SenderAddress))
+}