From c6c529438a759f3b9175be3c5351c420ded4063d Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Tue, 27 Feb 2024 18:16:09 -0500 Subject: [PATCH 1/4] fix!: override wasm default param after initialization (#2105) --- app/app.go | 22 ++++++++++++++++++---- app/wasm.go | 8 +++----- app/wasm_test.go | 9 +++------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/app/app.go b/app/app.go index 6dcd7b145..9acdfd0b3 100644 --- a/app/app.go +++ b/app/app.go @@ -324,7 +324,7 @@ func NewAxelarApp( upgradeKeeper: *getKeeper[upgradekeeper.Keeper](keepers), } - app.setUpgradeBehaviour(configurator) + app.setUpgradeBehaviour(configurator, keepers) // initialize stores app.MountKVStores(keys) @@ -447,11 +447,25 @@ func initMessageRouter(keepers *KeeperCache) nexusTypes.MessageRouter { return messageRouter } -func (app *AxelarApp) setUpgradeBehaviour(configurator module.Configurator) { +func (app *AxelarApp) setUpgradeBehaviour(configurator module.Configurator, keepers *KeeperCache) { app.upgradeKeeper.SetUpgradeHandler( upgradeName(app.Version()), func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { - return app.mm.RunMigrations(ctx, configurator, fromVM) + updatedVM, err := app.mm.RunMigrations(ctx, configurator, fromVM) + if err != nil { + return updatedVM, err + } + + // TODO: remove after v35 upgrade + // Override wasm module default params + if upgradeName(app.Version()) == "v0.35" && IsWasmEnabled() { + getKeeper[wasm.Keeper](keepers).SetParams(ctx, wasmtypes.Params{ + CodeUploadAccess: wasmtypes.AllowNobody, + InstantiateDefaultPermission: wasmtypes.AccessTypeNobody, + }) + } + + return updatedVM, err }, ) @@ -1035,7 +1049,7 @@ func GetModuleBasics() module.BasicManager { } if IsWasmEnabled() { - managers = append(managers, NewWasmAppModuleBasicOverride(wasm.AppModuleBasic{}, authtypes.NewModuleAddress(govtypes.ModuleName))) + managers = append(managers, NewWasmAppModuleBasicOverride(wasm.AppModuleBasic{})) } if IsIBCWasmHooksEnabled() { diff --git a/app/wasm.go b/app/wasm.go index 4ba22a2f7..d36c079e1 100644 --- a/app/wasm.go +++ b/app/wasm.go @@ -119,13 +119,11 @@ func isIBCSendPacketMsg(msg wasmvmtypes.CosmosMsg) bool { type WasmAppModuleBasicOverride struct { wasm.AppModuleBasic - uploader sdk.AccAddress } -func NewWasmAppModuleBasicOverride(wasmModule wasm.AppModuleBasic, uploader sdk.AccAddress) WasmAppModuleBasicOverride { +func NewWasmAppModuleBasicOverride(wasmModule wasm.AppModuleBasic) WasmAppModuleBasicOverride { return WasmAppModuleBasicOverride{ AppModuleBasic: wasmModule, - uploader: uploader, } } @@ -134,8 +132,8 @@ func NewWasmAppModuleBasicOverride(wasmModule wasm.AppModuleBasic, uploader sdk. func (m WasmAppModuleBasicOverride) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { return cdc.MustMarshalJSON(&wasm.GenesisState{ Params: wasmtypes.Params{ - CodeUploadAccess: wasmtypes.AccessTypeAnyOfAddresses.With(m.uploader), - InstantiateDefaultPermission: wasmtypes.AccessTypeAnyOfAddresses, + CodeUploadAccess: wasmtypes.AllowNobody, + InstantiateDefaultPermission: wasmtypes.AccessTypeNobody, }, }) } diff --git a/app/wasm_test.go b/app/wasm_test.go index a8fbbac4b..09423ef41 100644 --- a/app/wasm_test.go +++ b/app/wasm_test.go @@ -9,7 +9,6 @@ import ( wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/stretchr/testify/assert" @@ -242,8 +241,7 @@ func TestMsgTypeBlacklistMessenger_DispatchMsg(t *testing.T) { } func TestNewWasmAppModuleBasicOverride(t *testing.T) { - uploader := authtypes.NewModuleAddress("allowed to upload") - wasmModule := app.NewWasmAppModuleBasicOverride(wasm.AppModuleBasic{}, uploader) + wasmModule := app.NewWasmAppModuleBasicOverride(wasm.AppModuleBasic{}) cdc := app.MakeEncodingConfig().Codec genesis := wasmModule.DefaultGenesis(cdc) @@ -252,9 +250,8 @@ func TestNewWasmAppModuleBasicOverride(t *testing.T) { var state wasm.GenesisState assert.NoError(t, cdc.UnmarshalJSON(genesis, &state)) - assert.Equal(t, state.Params.InstantiateDefaultPermission, wasmtypes.AccessTypeAnyOfAddresses) - assert.True(t, state.Params.CodeUploadAccess.Allowed(uploader)) - assert.Len(t, state.Params.CodeUploadAccess.AllAuthorizedAddresses(), 1) + assert.Equal(t, state.Params.InstantiateDefaultPermission, wasmtypes.AccessTypeNobody) + assert.True(t, state.Params.CodeUploadAccess.Equals(wasmtypes.AllowNobody)) } func TestICSMiddleWare(t *testing.T) { From 4a0d1d7a90eca7c22b66bf410bb47d53e7f93cfa Mon Sep 17 00:00:00 2001 From: Sammy Date: Thu, 29 Feb 2024 03:10:24 +0800 Subject: [PATCH 2/4] refactor(vald): extract evm.go into multiple files (#2110) Co-authored-by: Christian Gorenflo --- vald/evm/decoders.go | 174 +++++ vald/evm/decoders_test.go | 151 ++++ vald/evm/deposit_confirmation.go | 79 +++ vald/evm/deposit_confirmation_test.go | 317 +++++++++ vald/evm/evm.go | 534 -------------- vald/evm/evm_test.go | 779 --------------------- vald/evm/gateway_tx_confirmation.go | 115 +++ vald/evm/gateway_txs_confirmation.go | 65 ++ vald/evm/key_transfer_confirmation.go | 69 ++ vald/evm/key_transfer_confirmation_test.go | 182 +++++ vald/evm/token_confirmation.go | 77 ++ vald/evm/token_confirmation_test.go | 217 ++++++ 12 files changed, 1446 insertions(+), 1313 deletions(-) create mode 100644 vald/evm/decoders.go create mode 100644 vald/evm/decoders_test.go create mode 100644 vald/evm/deposit_confirmation.go create mode 100644 vald/evm/deposit_confirmation_test.go create mode 100644 vald/evm/gateway_tx_confirmation.go create mode 100644 vald/evm/gateway_txs_confirmation.go create mode 100644 vald/evm/key_transfer_confirmation.go create mode 100644 vald/evm/key_transfer_confirmation_test.go create mode 100644 vald/evm/token_confirmation.go create mode 100644 vald/evm/token_confirmation_test.go diff --git a/vald/evm/decoders.go b/vald/evm/decoders.go new file mode 100644 index 000000000..33e9f3f96 --- /dev/null +++ b/vald/evm/decoders.go @@ -0,0 +1,174 @@ +package evm + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/axelarnetwork/axelar-core/x/evm/types" + nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" + "github.com/axelarnetwork/utils/funcs" + "github.com/axelarnetwork/utils/slices" +) + +// Smart contract event signatures +var ( + ERC20TransferSig = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")) + ERC20TokenDeploymentSig = crypto.Keccak256Hash([]byte("TokenDeployed(string,address)")) + MultisigTransferOperatorshipSig = crypto.Keccak256Hash([]byte("OperatorshipTransferred(bytes)")) + ContractCallSig = crypto.Keccak256Hash([]byte("ContractCall(address,string,string,bytes32,bytes)")) + ContractCallWithTokenSig = crypto.Keccak256Hash([]byte("ContractCallWithToken(address,string,string,bytes32,bytes,string,uint256)")) + TokenSentSig = crypto.Keccak256Hash([]byte("TokenSent(address,string,string,string,uint256)")) +) + +func DecodeERC20TransferEvent(log *geth.Log) (types.EventTransfer, error) { + if len(log.Topics) != 3 || log.Topics[0] != ERC20TransferSig { + return types.EventTransfer{}, fmt.Errorf("log is not an ERC20 transfer") + } + + uint256Type := funcs.Must(abi.NewType("uint256", "uint256", nil)) + + to := common.BytesToAddress(log.Topics[2][:]) + + arguments := abi.Arguments{ + {Type: uint256Type}, + } + + params, err := arguments.Unpack(log.Data) + if err != nil { + return types.EventTransfer{}, err + } + + return types.EventTransfer{ + To: types.Address(to), + Amount: sdk.NewUintFromBigInt(params[0].(*big.Int)), + }, nil +} + +func DecodeERC20TokenDeploymentEvent(log *geth.Log) (types.EventTokenDeployed, error) { + if len(log.Topics) != 1 || log.Topics[0] != ERC20TokenDeploymentSig { + return types.EventTokenDeployed{}, fmt.Errorf("event is not for an ERC20 token deployment") + } + + stringType := funcs.Must(abi.NewType("string", "string", nil)) + addressType := funcs.Must(abi.NewType("address", "address", nil)) + + arguments := abi.Arguments{{Type: stringType}, {Type: addressType}} + params, err := types.StrictDecode(arguments, log.Data) + if err != nil { + return types.EventTokenDeployed{}, err + } + + return types.EventTokenDeployed{ + Symbol: params[0].(string), + TokenAddress: types.Address(params[1].(common.Address)), + }, nil +} + +func DecodeMultisigOperatorshipTransferredEvent(log *geth.Log) (types.EventMultisigOperatorshipTransferred, error) { + if len(log.Topics) != 1 || log.Topics[0] != MultisigTransferOperatorshipSig { + return types.EventMultisigOperatorshipTransferred{}, fmt.Errorf("event is not OperatorshipTransferred") + } + + bytesType := funcs.Must(abi.NewType("bytes", "bytes", nil)) + newOperatorsData, err := types.StrictDecode(abi.Arguments{{Type: bytesType}}, log.Data) + if err != nil { + return types.EventMultisigOperatorshipTransferred{}, err + } + + addressesType := funcs.Must(abi.NewType("address[]", "address[]", nil)) + uint256ArrayType := funcs.Must(abi.NewType("uint256[]", "uint256[]", nil)) + uint256Type := funcs.Must(abi.NewType("uint256", "uint256", nil)) + + arguments := abi.Arguments{{Type: addressesType}, {Type: uint256ArrayType}, {Type: uint256Type}} + params, err := types.StrictDecode(arguments, newOperatorsData[0].([]byte)) + if err != nil { + return types.EventMultisigOperatorshipTransferred{}, err + } + + event := types.EventMultisigOperatorshipTransferred{ + NewOperators: slices.Map(params[0].([]common.Address), func(addr common.Address) types.Address { return types.Address(addr) }), + NewWeights: slices.Map(params[1].([]*big.Int), sdk.NewUintFromBigInt), + NewThreshold: sdk.NewUintFromBigInt(params[2].(*big.Int)), + } + + return event, nil +} + +func DecodeEventContractCallWithToken(log *geth.Log) (types.EventContractCallWithToken, error) { + stringType := funcs.Must(abi.NewType("string", "string", nil)) + bytesType := funcs.Must(abi.NewType("bytes", "bytes", nil)) + uint256Type := funcs.Must(abi.NewType("uint256", "uint256", nil)) + + arguments := abi.Arguments{ + {Type: stringType}, + {Type: stringType}, + {Type: bytesType}, + {Type: stringType}, + {Type: uint256Type}, + } + params, err := types.StrictDecode(arguments, log.Data) + if err != nil { + return types.EventContractCallWithToken{}, err + } + + return types.EventContractCallWithToken{ + Sender: types.Address(common.BytesToAddress(log.Topics[1].Bytes())), + DestinationChain: nexus.ChainName(params[0].(string)), + ContractAddress: params[1].(string), + PayloadHash: types.Hash(common.BytesToHash(log.Topics[2].Bytes())), + Symbol: params[3].(string), + Amount: sdk.NewUintFromBigInt(params[4].(*big.Int)), + }, nil +} + +func DecodeEventTokenSent(log *geth.Log) (types.EventTokenSent, error) { + stringType := funcs.Must(abi.NewType("string", "string", nil)) + uint256Type := funcs.Must(abi.NewType("uint256", "uint256", nil)) + + arguments := abi.Arguments{ + {Type: stringType}, + {Type: stringType}, + {Type: stringType}, + {Type: uint256Type}, + } + params, err := types.StrictDecode(arguments, log.Data) + if err != nil { + return types.EventTokenSent{}, err + } + + return types.EventTokenSent{ + Sender: types.Address(common.BytesToAddress(log.Topics[1].Bytes())), + DestinationChain: nexus.ChainName(params[0].(string)), + DestinationAddress: params[1].(string), + Symbol: params[2].(string), + Amount: sdk.NewUintFromBigInt(params[3].(*big.Int)), + }, nil +} + +func DecodeEventContractCall(log *geth.Log) (types.EventContractCall, error) { + stringType := funcs.Must(abi.NewType("string", "string", nil)) + bytesType := funcs.Must(abi.NewType("bytes", "bytes", nil)) + + arguments := abi.Arguments{ + {Type: stringType}, + {Type: stringType}, + {Type: bytesType}, + } + params, err := types.StrictDecode(arguments, log.Data) + if err != nil { + return types.EventContractCall{}, err + } + + return types.EventContractCall{ + Sender: types.Address(common.BytesToAddress(log.Topics[1].Bytes())), + DestinationChain: nexus.ChainName(params[0].(string)), + ContractAddress: params[1].(string), + PayloadHash: types.Hash(common.BytesToHash(log.Topics[2].Bytes())), + }, nil +} diff --git a/vald/evm/decoders_test.go b/vald/evm/decoders_test.go new file mode 100644 index 000000000..36b58aa9b --- /dev/null +++ b/vald/evm/decoders_test.go @@ -0,0 +1,151 @@ +package evm_test + +import ( + "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + + "github.com/axelarnetwork/axelar-core/testutils/rand" + "github.com/axelarnetwork/axelar-core/vald/evm" + "github.com/axelarnetwork/axelar-core/x/evm/types" +) + +func TestDecodeEventTokenSent(t *testing.T) { + log := &geth.Log{ + Topics: []common.Hash{ + common.HexToHash("0x651d93f66c4329630e8d0f62488eff599e3be484da587335e8dc0fcf46062726"), + common.HexToHash("0x00000000000000000000000068b93045fe7d8794a7caf327e7f855cd6cd03bb8"), + }, + Data: common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000000a657468657265756d2d3200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783538656134313033656439353564434262646338613066456261626133393542366534346431354600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f657468657265756d2d312d7561786c0000000000000000000000000000000000"), + } + + expected := types.EventTokenSent{ + Sender: types.Address(common.HexToAddress("0x68B93045fe7D8794a7cAF327e7f855CD6Cd03BB8")), + DestinationChain: "ethereum-2", + DestinationAddress: "0x58ea4103ed955dCBbdc8a0fEbaba395B6e44d15F", + Symbol: "ethereum-1-uaxl", + Amount: sdk.NewUint(10000000), + } + actual, err := evm.DecodeEventTokenSent(log) + + assert.NoError(t, err) + assert.Equal(t, expected, actual) +} + +func TestDecodeEventContractCall(t *testing.T) { + log := &geth.Log{ + Topics: []common.Hash{ + common.HexToHash("0x30ae6cc78c27e651745bf2ad08a11de83910ac1e347a52f7ac898c0fbef94dae"), + common.HexToHash("0x000000000000000000000000d48e199950589a4336e4dc43bd2c72ba0c0baa86"), + common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e"), + }, + Data: common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000a657468657265756d2d3200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078623938343566393234376138354565353932323733613739363035663334453836303764376537350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066275666665720000000000000000000000000000000000000000000000000000"), + } + + expected := types.EventContractCall{ + Sender: types.Address(common.HexToAddress("0xD48E199950589A4336E4dc43bd2C72Ba0C0baA86")), + DestinationChain: "ethereum-2", + ContractAddress: "0xb9845f9247a85Ee592273a79605f34E8607d7e75", + PayloadHash: types.Hash(common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e")), + } + actual, err := evm.DecodeEventContractCall(log) + + assert.NoError(t, err) + assert.Equal(t, expected, actual) +} + +func TestDecodeEventContractCallWithToken(t *testing.T) { + log := &geth.Log{ + Topics: []common.Hash{ + common.HexToHash("0x7e50569d26be643bda7757722291ec66b1be66d8283474ae3fab5a98f878a7a2"), + common.HexToHash("0x00000000000000000000000068b93045fe7d8794a7caf327e7f855cd6cd03bb8"), + common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e"), + }, + Data: common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000000000000000000008657468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307837366130363034333339313731326245333941333433643166343331363538353466434636446533000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006627566666572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047561786c00000000000000000000000000000000000000000000000000000000"), + } + + expected := types.EventContractCallWithToken{ + Sender: types.Address(common.HexToAddress("0x68B93045fe7D8794a7cAF327e7f855CD6Cd03BB8")), + DestinationChain: "ethereum", + ContractAddress: "0x76a06043391712bE39A343d1f43165854fCF6De3", + PayloadHash: types.Hash(common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e")), + Symbol: "uaxl", + Amount: sdk.NewUint(10000000), + } + actual, err := evm.DecodeEventContractCallWithToken(log) + + assert.NoError(t, err) + assert.Equal(t, expected, actual) +} + +func TestDecodeTokenDeployEvent_CorrectData(t *testing.T) { + axelarGateway := common.HexToAddress("0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA") + + tokenDeploySig := evm.ERC20TokenDeploymentSig + expectedAddr := common.HexToAddress("0xE7481ECB61F9C84b91C03414F3D5d48E5436045D") + expectedSymbol := "XPTO" + data := common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e7481ecb61f9c84b91c03414f3d5d48e5436045d00000000000000000000000000000000000000000000000000000000000000045850544f00000000000000000000000000000000000000000000000000000000") + + l := &geth.Log{Address: axelarGateway, Data: data, Topics: []common.Hash{tokenDeploySig}} + + tokenDeployed, err := evm.DecodeERC20TokenDeploymentEvent(l) + assert.NoError(t, err) + assert.Equal(t, expectedSymbol, tokenDeployed.Symbol) + assert.Equal(t, types.Address(expectedAddr), tokenDeployed.TokenAddress) +} + +func TestDecodeErc20TransferEvent_NotErc20Transfer(t *testing.T) { + l := geth.Log{ + Topics: []common.Hash{ + common.BytesToHash(rand.Bytes(common.HashLength)), + common.BytesToHash(common.LeftPadBytes(common.BytesToAddress(rand.Bytes(common.AddressLength)).Bytes(), common.HashLength)), + common.BytesToHash(common.LeftPadBytes(common.BytesToAddress(rand.Bytes(common.AddressLength)).Bytes(), common.HashLength)), + }, + Data: common.LeftPadBytes(big.NewInt(2).Bytes(), common.HashLength), + } + + _, err := evm.DecodeERC20TransferEvent(&l) + assert.Error(t, err) +} + +func TestDecodeErc20TransferEvent_InvalidErc20Transfer(t *testing.T) { + erc20TransferEventSig := common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + + l := geth.Log{ + Topics: []common.Hash{ + erc20TransferEventSig, + common.BytesToHash(common.LeftPadBytes(common.BytesToAddress(rand.Bytes(common.AddressLength)).Bytes(), common.HashLength)), + }, + Data: common.LeftPadBytes(big.NewInt(2).Bytes(), common.HashLength), + } + + _, err := evm.DecodeERC20TransferEvent(&l) + + assert.Error(t, err) +} + +func TestDecodeErc20TransferEvent_CorrectData(t *testing.T) { + erc20TransferEventSig := common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + expectedFrom := common.BytesToAddress(rand.Bytes(common.AddressLength)) + expectedTo := common.BytesToAddress(rand.Bytes(common.AddressLength)) + expectedAmount := sdk.NewUint(uint64(rand.I64Between(1, 10000))) + + l := geth.Log{ + Topics: []common.Hash{ + erc20TransferEventSig, + common.BytesToHash(common.LeftPadBytes(expectedFrom.Bytes(), common.HashLength)), + common.BytesToHash(common.LeftPadBytes(expectedTo.Bytes(), common.HashLength)), + }, + Data: common.LeftPadBytes(expectedAmount.BigInt().Bytes(), common.HashLength), + } + + transfer, err := evm.DecodeERC20TransferEvent(&l) + + assert.NoError(t, err) + assert.Equal(t, types.Address(expectedTo), transfer.To) + assert.Equal(t, expectedAmount, transfer.Amount) +} diff --git a/vald/evm/deposit_confirmation.go b/vald/evm/deposit_confirmation.go new file mode 100644 index 000000000..417f50081 --- /dev/null +++ b/vald/evm/deposit_confirmation.go @@ -0,0 +1,79 @@ +package evm + +import ( + "bytes" + "context" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + + "github.com/axelarnetwork/axelar-core/x/evm/types" + voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" +) + +// ProcessDepositConfirmation votes on the correctness of an EVM chain token deposit +func (mgr Mgr) ProcessDepositConfirmation(event *types.ConfirmDepositStarted) error { + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring deposit confirmation poll: not a participant") + return nil + } + + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) + + return err + } + + events := mgr.processDepositConfirmationLogs(event, txReceipt.Logs) + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err +} + +func (mgr Mgr) processDepositConfirmationLogs(event *types.ConfirmDepositStarted, logs []*geth.Log) []types.Event { + events := make([]types.Event, 0, len(logs)) + + for i, log := range logs { + if log.Topics[0] != ERC20TransferSig { + continue + } + + if !bytes.Equal(event.TokenAddress.Bytes(), log.Address.Bytes()) { + continue + } + + erc20Event, err := DecodeERC20TransferEvent(log) + if err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "decode event Transfer failed").Error()) + continue + } + + if erc20Event.To != event.DepositAddress { + continue + } + + if err := erc20Event.ValidateBasic(); err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event Transfer").Error()) + continue + } + + events = append(events, types.Event{ + Chain: event.Chain, + TxID: event.TxID, + Index: uint64(i), + Event: &types.Event_Transfer{ + Transfer: &erc20Event, + }, + }) + } + + return events +} diff --git a/vald/evm/deposit_confirmation_test.go b/vald/evm/deposit_confirmation_test.go new file mode 100644 index 000000000..0fc50d1cd --- /dev/null +++ b/vald/evm/deposit_confirmation_test.go @@ -0,0 +1,317 @@ +package evm_test + +import ( + "context" + "math/big" + "strings" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + + broadcastmock "github.com/axelarnetwork/axelar-core/sdk-utils/broadcast/mock" + "github.com/axelarnetwork/axelar-core/testutils/rand" + "github.com/axelarnetwork/axelar-core/vald/evm" + evmmock "github.com/axelarnetwork/axelar-core/vald/evm/mock" + evmRpc "github.com/axelarnetwork/axelar-core/vald/evm/rpc" + "github.com/axelarnetwork/axelar-core/vald/evm/rpc/mock" + "github.com/axelarnetwork/axelar-core/x/evm/types" + "github.com/axelarnetwork/axelar-core/x/evm/types/testutils" + nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" + votetypes "github.com/axelarnetwork/axelar-core/x/vote/types" + . "github.com/axelarnetwork/utils/test" +) + +func TestMgr_ProccessDepositConfirmation(t *testing.T) { + var ( + mgr *evm.Mgr + receipt *geth.Receipt + tokenAddr types.Address + depositAddr types.Address + amount sdk.Uint + evmMap map[string]evmRpc.Client + rpc *mock.ClientMock + valAddr sdk.ValAddress + + votes []*types.VoteEvents + err error + ) + + givenDeposit := Given("a deposit has been made", func() { + amount = sdk.NewUint(uint64(rand.PosI64())) + tokenAddr = testutils.RandomAddress() + depositAddr = testutils.RandomAddress() + randomTokenDeposit := &geth.Log{ + Address: common.Address(testutils.RandomAddress()), + Topics: []common.Hash{ + evm.ERC20TransferSig, + padToHash(testutils.RandomAddress()), + padToHash(depositAddr), + }, + Data: padToHash(big.NewInt(rand.PosI64())).Bytes(), + } + randomEvent := &geth.Log{ + Address: common.Address(tokenAddr), + Topics: []common.Hash{ + common.Hash(testutils.RandomHash()), + padToHash(testutils.RandomAddress()), + padToHash(depositAddr), + }, + Data: padToHash(big.NewInt(rand.PosI64())).Bytes(), + } + + invalidDeposit := &geth.Log{ + Address: common.Address(tokenAddr), + Topics: []common.Hash{ + evm.ERC20TransferSig, + padToHash(testutils.RandomAddress()), + }, + Data: padToHash(big.NewInt(rand.PosI64())).Bytes(), + } + + zeroAmountDeposit := &geth.Log{ + Address: common.Address(tokenAddr), + Topics: []common.Hash{ + evm.ERC20TransferSig, + padToHash(testutils.RandomAddress()), + padToHash(depositAddr), + }, + Data: padToHash(big.NewInt(0)).Bytes(), + } + + validDeposit := &geth.Log{ + Address: common.Address(tokenAddr), + Topics: []common.Hash{ + evm.ERC20TransferSig, + padToHash(testutils.RandomAddress()), + padToHash(depositAddr), + }, + Data: padToHash(amount.BigInt()).Bytes(), + } + + receipt = &geth.Receipt{ + TxHash: common.Hash(testutils.RandomHash()), + BlockNumber: big.NewInt(rand.PosI64()), + Logs: []*geth.Log{randomTokenDeposit, validDeposit, randomEvent, invalidDeposit, zeroAmountDeposit}, + Status: 1, + } + }) + + confirmingDeposit := When("confirming the existing deposit", func() { + event := testutils.RandomConfirmDepositStarted() + event.TxID = types.Hash(receipt.TxHash) + evmMap[strings.ToLower(event.Chain.String())] = rpc + event.DepositAddress = depositAddr + event.TokenAddress = tokenAddr + event.Participants = append(event.Participants, valAddr) + + err = mgr.ProcessDepositConfirmation(&event) + }) + + reject := Then("reject the confirmation", func(t *testing.T) { + assert.Len(t, votes, 1) + assert.Len(t, votes[0].Events, 0) + }) + + noError := Then("return no error", func(t *testing.T) { + assert.NoError(t, err) + }) + + Given("an evm manager", func() { + votes = []*types.VoteEvents{} + evmMap = make(map[string]evmRpc.Client, 1) + + broadcaster := &broadcastmock.BroadcasterMock{ + BroadcastFunc: func(_ context.Context, msgs ...sdk.Msg) (*sdk.TxResponse, error) { + for _, msg := range msgs { + votes = append(votes, msg.(*votetypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents)) + } + return nil, nil + }, + } + + valAddr = rand.ValAddr() + mgr = evm.NewMgr(evmMap, broadcaster, valAddr, rand.AccAddr(), &evmmock.LatestFinalizedBlockCacheMock{ + GetFunc: func(_ nexus.ChainName) *big.Int { return big.NewInt(0) }, + SetFunc: func(_ nexus.ChainName, _ *big.Int) {}, + }) + }). + Given("an evm rpc client", func() { + rpc = &mock.ClientMock{ + HeaderByNumberFunc: func(context.Context, *big.Int) (*evmRpc.Header, error) { + return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil + }, + TransactionReceiptFunc: func(_ context.Context, txID common.Hash) (*geth.Receipt, error) { + if txID != receipt.TxHash { + return nil, ethereum.NotFound + } + return receipt, nil + }, + LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { + return receipt.BlockNumber, nil + }, + } + }). + Branch( + Given("no deposit has been made", func() { + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { + return nil, ethereum.NotFound + } + }). + When("confirming a random deposit on the correct chain", func() { + event := testutils.RandomConfirmDepositStarted() + event.Participants = append(event.Participants, valAddr) + + evmMap[strings.ToLower(event.Chain.String())] = rpc + err = mgr.ProcessDepositConfirmation(&event) + }). + Then2(noError). + Then2(reject), + + givenDeposit. + When("confirming the deposit on unsupported chain", func() { + event := testutils.RandomConfirmDepositStarted() + event.TxID = types.Hash(receipt.TxHash) + event.DepositAddress = depositAddr + event.TokenAddress = tokenAddr + event.Participants = append(event.Participants, valAddr) + + err = mgr.ProcessDepositConfirmation(&event) + + }). + Then("return error", func(t *testing.T) { + assert.Error(t, err) + }), + + givenDeposit. + Given("confirmation height is not reached yet", func() { + rpc.LatestFinalizedBlockNumberFunc = func(ctx context.Context, confirmations uint64) (*big.Int, error) { + return sdk.NewIntFromBigInt(receipt.BlockNumber).SubRaw(int64(confirmations)).BigInt(), nil + } + }). + When2(confirmingDeposit). + Then2(noError). + Then2(reject), + + givenDeposit. + When2(confirmingDeposit). + Then2(noError). + Then("accept the confirmation", func(t *testing.T) { + assert.Len(t, votes, 1) + assert.Len(t, votes[0].Events, 1) + transferEvent, ok := votes[0].Events[0].Event.(*types.Event_Transfer) + assert.True(t, ok) + + assert.Equal(t, depositAddr, transferEvent.Transfer.To) + assert.True(t, transferEvent.Transfer.Amount.Equal(amount)) + }), + + givenDeposit. + When("confirming event with wrong tx ID", func() { + event := testutils.RandomConfirmDepositStarted() + evmMap[strings.ToLower(event.Chain.String())] = rpc + event.DepositAddress = depositAddr + event.TokenAddress = tokenAddr + event.Participants = append(event.Participants, valAddr) + + err = mgr.ProcessDepositConfirmation(&event) + }). + Then2(noError). + Then2(reject), + + givenDeposit. + When("confirming event with wrong token address", func() { + event := testutils.RandomConfirmDepositStarted() + event.TxID = types.Hash(receipt.TxHash) + evmMap[strings.ToLower(event.Chain.String())] = rpc + event.DepositAddress = depositAddr + event.Participants = append(event.Participants, valAddr) + + err = mgr.ProcessDepositConfirmation(&event) + }). + Then2(noError). + Then2(reject), + + givenDeposit. + When("confirming event with wrong deposit address", func() { + event := testutils.RandomConfirmDepositStarted() + event.TxID = types.Hash(receipt.TxHash) + evmMap[strings.ToLower(event.Chain.String())] = rpc + event.TokenAddress = tokenAddr + event.Participants = append(event.Participants, valAddr) + + err = mgr.ProcessDepositConfirmation(&event) + }). + Then2(noError). + Then2(reject), + + givenDeposit. + When("confirming a deposit without being a participant", func() { + event := testutils.RandomConfirmDepositStarted() + event.TxID = types.Hash(receipt.TxHash) + evmMap[strings.ToLower(event.Chain.String())] = rpc + event.DepositAddress = depositAddr + event.TokenAddress = tokenAddr + + err = mgr.ProcessDepositConfirmation(&event) + }). + Then2(noError). + Then("do nothing", func(t *testing.T) { + assert.Len(t, votes, 0) + }), + + givenDeposit. + Given("multiple deposits in a single tx", func() { + additionalAmount := sdk.NewUint(uint64(rand.PosI64())) + amount = amount.Add(additionalAmount) + additionalDeposit := &geth.Log{ + Address: common.Address(tokenAddr), + Topics: []common.Hash{ + evm.ERC20TransferSig, + padToHash(testutils.RandomAddress()), + padToHash(depositAddr), + }, + Data: padToHash(additionalAmount.BigInt()).Bytes(), + } + receipt.Logs = append(receipt.Logs, additionalDeposit) + }). + When("confirming the deposits", func() { + event := testutils.RandomConfirmDepositStarted() + event.TxID = types.Hash(receipt.TxHash) + evmMap[strings.ToLower(event.Chain.String())] = rpc + event.DepositAddress = depositAddr + event.TokenAddress = tokenAddr + event.Participants = append(event.Participants, valAddr) + + err = mgr.ProcessDepositConfirmation(&event) + }). + Then2(noError). + Then("vote for all deposits in the same tx", func(t *testing.T) { + assert.Len(t, votes, 1) + assert.Len(t, votes[0].Events, 2) + + actualAmount := sdk.ZeroUint() + for _, event := range votes[0].Events { + transferEvent, ok := event.Event.(*types.Event_Transfer) + assert.True(t, ok) + assert.Equal(t, depositAddr, transferEvent.Transfer.To) + + actualAmount = actualAmount.Add(transferEvent.Transfer.Amount) + } + + assert.True(t, actualAmount.Equal(amount)) + }), + ). + Run(t, 20) +} + +type byter interface { + Bytes() []byte +} + +func padToHash[T byter](x T) common.Hash { + return common.BytesToHash(common.LeftPadBytes(x.Bytes(), common.HashLength)) +} diff --git a/vald/evm/evm.go b/vald/evm/evm.go index 556490e9d..ccf19e885 100644 --- a/vald/evm/evm.go +++ b/vald/evm/evm.go @@ -1,44 +1,27 @@ package evm import ( - "bytes" "context" goerrors "errors" "fmt" - "math/big" "strings" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" geth "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/axelarnetwork/axelar-core/sdk-utils/broadcast" "github.com/axelarnetwork/axelar-core/utils/errors" "github.com/axelarnetwork/axelar-core/vald/evm/rpc" "github.com/axelarnetwork/axelar-core/x/evm/types" nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" - vote "github.com/axelarnetwork/axelar-core/x/vote/exported" - voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" - "github.com/axelarnetwork/utils/funcs" "github.com/axelarnetwork/utils/log" rs "github.com/axelarnetwork/utils/monads/results" "github.com/axelarnetwork/utils/slices" ) -// Smart contract event signatures -var ( - ERC20TransferSig = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")) - ERC20TokenDeploymentSig = crypto.Keccak256Hash([]byte("TokenDeployed(string,address)")) - MultisigTransferOperatorshipSig = crypto.Keccak256Hash([]byte("OperatorshipTransferred(bytes)")) - ContractCallSig = crypto.Keccak256Hash([]byte("ContractCall(address,string,string,bytes32,bytes)")) - ContractCallWithTokenSig = crypto.Keccak256Hash([]byte("ContractCallWithToken(address,string,string,bytes32,bytes,string,uint256)")) - TokenSentSig = crypto.Keccak256Hash([]byte("TokenSent(address,string,string,string,uint256)")) -) - // ErrNotFinalized is returned when a transaction is not finalized var ErrNotFinalized = goerrors.New("not finalized") @@ -76,355 +59,6 @@ func (mgr Mgr) ProcessNewChain(event *types.ChainAdded) (err error) { return nil } -// ProcessDepositConfirmation votes on the correctness of an EVM chain token deposit -func (mgr Mgr) ProcessDepositConfirmation(event *types.ConfirmDepositStarted) error { - if !mgr.isParticipantOf(event.Participants) { - mgr.logger("pollID", event.PollID).Debug("ignoring deposit confirmation poll: not a participant") - return nil - } - - txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) - if err != nil { - return err - } - if txReceipt == nil { - mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) - _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) - - return err - } - - var events []types.Event - for i, log := range txReceipt.Logs { - if log.Topics[0] != ERC20TransferSig { - continue - } - - if !bytes.Equal(event.TokenAddress.Bytes(), log.Address.Bytes()) { - continue - } - - erc20Event, err := DecodeERC20TransferEvent(log) - if err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "decode event Transfer failed").Error()) - continue - } - - if erc20Event.To != event.DepositAddress { - continue - } - - if err := erc20Event.ValidateBasic(); err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event Transfer").Error()) - continue - } - - events = append(events, types.Event{ - Chain: event.Chain, - TxID: event.TxID, - Index: uint64(i), - Event: &types.Event_Transfer{ - Transfer: &erc20Event, - }, - }) - } - - mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) - _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) - - return err -} - -// ProcessTokenConfirmation votes on the correctness of an EVM chain token deployment -func (mgr Mgr) ProcessTokenConfirmation(event *types.ConfirmTokenStarted) error { - if !mgr.isParticipantOf(event.Participants) { - mgr.logger("pollID", event.PollID).Debug("ignoring token confirmation poll: not a participant") - return nil - } - - txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) - if err != nil { - return err - } - if txReceipt == nil { - mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) - _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) - - return err - } - - var events []types.Event - for i, log := range txReceipt.Logs { - if log.Topics[0] != ERC20TokenDeploymentSig { - continue - } - - if !bytes.Equal(event.GatewayAddress.Bytes(), log.Address.Bytes()) { - continue - } - - erc20Event, err := DecodeERC20TokenDeploymentEvent(log) - if err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "decode event TokenDeployed failed").Error()) - continue - } - - if erc20Event.TokenAddress != event.TokenAddress || erc20Event.Symbol != event.TokenDetails.Symbol { - continue - } - - if err := erc20Event.ValidateBasic(); err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event ERC20TokenDeployment").Error()) - continue - } - - events = append(events, types.Event{ - Chain: event.Chain, - TxID: event.TxID, - Index: uint64(i), - Event: &types.Event_TokenDeployed{ - TokenDeployed: &erc20Event, - }, - }) - break - } - - mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) - _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) - - return err -} - -// ProcessTransferKeyConfirmation votes on the correctness of an EVM chain key transfer -func (mgr Mgr) ProcessTransferKeyConfirmation(event *types.ConfirmKeyTransferStarted) error { - if !mgr.isParticipantOf(event.Participants) { - mgr.logger("pollID", event.PollID).Debug("ignoring key transfer confirmation poll: not a participant") - return nil - } - - txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) - if err != nil { - return err - } - if txReceipt == nil { - mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) - _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) - - return err - } - - var events []types.Event - for i := len(txReceipt.Logs) - 1; i >= 0; i-- { - txlog := txReceipt.Logs[i] - - if txlog.Topics[0] != MultisigTransferOperatorshipSig { - continue - } - - // Event is not emitted by the axelar gateway - if txlog.Address != common.Address(event.GatewayAddress) { - continue - } - - transferOperatorshipEvent, err := DecodeMultisigOperatorshipTransferredEvent(txlog) - if err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "failed decoding operatorship transferred event").Error()) - continue - } - - if err := transferOperatorshipEvent.ValidateBasic(); err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event MultisigTransferOperatorship").Error()) - continue - } - - events = append(events, types.Event{ - Chain: event.Chain, - TxID: event.TxID, - Index: uint64(i), - Event: &types.Event_MultisigOperatorshipTransferred{ - MultisigOperatorshipTransferred: &transferOperatorshipEvent, - }}) - break - } - - mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) - _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) - - return err -} - -// ProcessGatewayTxConfirmation votes on the correctness of an EVM chain gateway's transactions -func (mgr Mgr) ProcessGatewayTxConfirmation(event *types.ConfirmGatewayTxStarted) error { - if !mgr.isParticipantOf(event.Participants) { - mgr.logger("pollID", event.PollID).Debug("ignoring gateway tx confirmation poll: not a participant") - return nil - } - - txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) - if err != nil { - return err - } - if txReceipt == nil { - mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) - _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) - - return err - } - - events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, txReceipt.Logs) - mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) - _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) - - return err -} - -// ProcessGatewayTxsConfirmation votes on the correctness of an EVM chain multiple gateway transactions -func (mgr Mgr) ProcessGatewayTxsConfirmation(event *types.ConfirmGatewayTxsStarted) error { - if !mgr.isParticipantOf(event.Participants) { - pollIDs := slices.Map(event.PollMappings, func(m types.PollMapping) vote.PollID { return m.PollID }) - mgr.logger("poll_ids", pollIDs).Debug("ignoring gateway txs confirmation poll: not a participant") - return nil - } - - txIDs := slices.Map(event.PollMappings, func(poll types.PollMapping) common.Hash { return common.Hash(poll.TxID) }) - txReceipts, err := mgr.GetTxReceiptsIfFinalized(event.Chain, txIDs, event.ConfirmationHeight) - if err != nil { - return err - } - - var votes []sdk.Msg - for i, result := range txReceipts { - pollID := event.PollMappings[i].PollID - txID := event.PollMappings[i].TxID - - logger := mgr.logger("chain", event.Chain, "poll_id", pollID.String(), "tx_id", txID.Hex()) - - // only broadcast empty votes if the tx is not found or not finalized - switch result.Err() { - case nil: - events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, result.Ok().Logs) - logger.Infof("broadcasting vote %v", events) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain, events...))) - case ErrNotFinalized: - logger.Debug(fmt.Sprintf("transaction %s not finalized", txID.Hex())) - logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) - case ErrTxFailed: - logger.Debug(fmt.Sprintf("transaction %s failed", txID.Hex())) - logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) - case ethereum.NotFound: - logger.Debug(fmt.Sprintf("transaction receipt %s not found", txID.Hex())) - logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) - votes = append(votes, voteTypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) - default: - logger.Errorf("failed to get tx receipt: %s", result.Err().Error()) - } - - } - - _, err = mgr.broadcaster.Broadcast(context.TODO(), votes...) - return err -} - -func DecodeEventTokenSent(log *geth.Log) (types.EventTokenSent, error) { - stringType, err := abi.NewType("string", "string", nil) - if err != nil { - return types.EventTokenSent{}, err - } - - uint256Type, err := abi.NewType("uint256", "uint256", nil) - if err != nil { - return types.EventTokenSent{}, err - } - - arguments := abi.Arguments{ - {Type: stringType}, - {Type: stringType}, - {Type: stringType}, - {Type: uint256Type}, - } - params, err := types.StrictDecode(arguments, log.Data) - if err != nil { - return types.EventTokenSent{}, err - } - - return types.EventTokenSent{ - Sender: types.Address(common.BytesToAddress(log.Topics[1].Bytes())), - DestinationChain: nexus.ChainName(params[0].(string)), - DestinationAddress: params[1].(string), - Symbol: params[2].(string), - Amount: sdk.NewUintFromBigInt(params[3].(*big.Int)), - }, nil -} - -func DecodeEventContractCall(log *geth.Log) (types.EventContractCall, error) { - stringType, err := abi.NewType("string", "string", nil) - if err != nil { - return types.EventContractCall{}, err - } - - bytesType, err := abi.NewType("bytes", "bytes", nil) - if err != nil { - return types.EventContractCall{}, err - } - - arguments := abi.Arguments{ - {Type: stringType}, - {Type: stringType}, - {Type: bytesType}, - } - params, err := types.StrictDecode(arguments, log.Data) - if err != nil { - return types.EventContractCall{}, err - } - - return types.EventContractCall{ - Sender: types.Address(common.BytesToAddress(log.Topics[1].Bytes())), - DestinationChain: nexus.ChainName(params[0].(string)), - ContractAddress: params[1].(string), - PayloadHash: types.Hash(common.BytesToHash(log.Topics[2].Bytes())), - }, nil -} - -func DecodeEventContractCallWithToken(log *geth.Log) (types.EventContractCallWithToken, error) { - stringType, err := abi.NewType("string", "string", nil) - if err != nil { - return types.EventContractCallWithToken{}, err - } - - bytesType, err := abi.NewType("bytes", "bytes", nil) - if err != nil { - return types.EventContractCallWithToken{}, err - } - - uint256Type, err := abi.NewType("uint256", "uint256", nil) - if err != nil { - return types.EventContractCallWithToken{}, err - } - - arguments := abi.Arguments{ - {Type: stringType}, - {Type: stringType}, - {Type: bytesType}, - {Type: stringType}, - {Type: uint256Type}, - } - params, err := types.StrictDecode(arguments, log.Data) - if err != nil { - return types.EventContractCallWithToken{}, err - } - - return types.EventContractCallWithToken{ - Sender: types.Address(common.BytesToAddress(log.Topics[1].Bytes())), - DestinationChain: nexus.ChainName(params[0].(string)), - ContractAddress: params[1].(string), - PayloadHash: types.Hash(common.BytesToHash(log.Topics[2].Bytes())), - Symbol: params[3].(string), - Amount: sdk.NewUintFromBigInt(params[4].(*big.Int)), - }, nil -} - func (mgr Mgr) isTxReceiptFinalized(chain nexus.ChainName, txReceipt *geth.Receipt, confHeight uint64) (bool, error) { client, ok := mgr.rpcs[strings.ToLower(chain.String())] if !ok { @@ -520,174 +154,6 @@ func (mgr Mgr) GetTxReceiptsIfFinalized(chain nexus.ChainName, txIDs []common.Ha }), nil } -func DecodeERC20TransferEvent(log *geth.Log) (types.EventTransfer, error) { - if len(log.Topics) != 3 || log.Topics[0] != ERC20TransferSig { - return types.EventTransfer{}, fmt.Errorf("log is not an ERC20 transfer") - } - - uint256Type, err := abi.NewType("uint256", "uint256", nil) - if err != nil { - return types.EventTransfer{}, err - } - - to := common.BytesToAddress(log.Topics[2][:]) - - arguments := abi.Arguments{ - {Type: uint256Type}, - } - - params, err := arguments.Unpack(log.Data) - if err != nil { - return types.EventTransfer{}, err - } - - return types.EventTransfer{ - To: types.Address(to), - Amount: sdk.NewUintFromBigInt(params[0].(*big.Int)), - }, nil -} - -func DecodeERC20TokenDeploymentEvent(log *geth.Log) (types.EventTokenDeployed, error) { - if len(log.Topics) != 1 || log.Topics[0] != ERC20TokenDeploymentSig { - return types.EventTokenDeployed{}, fmt.Errorf("event is not for an ERC20 token deployment") - } - - // Decode the data field - stringType, err := abi.NewType("string", "string", nil) - if err != nil { - return types.EventTokenDeployed{}, err - } - addressType, err := abi.NewType("address", "address", nil) - if err != nil { - return types.EventTokenDeployed{}, err - } - - arguments := abi.Arguments{{Type: stringType}, {Type: addressType}} - params, err := types.StrictDecode(arguments, log.Data) - if err != nil { - return types.EventTokenDeployed{}, err - } - - return types.EventTokenDeployed{ - Symbol: params[0].(string), - TokenAddress: types.Address(params[1].(common.Address)), - }, nil -} - -func DecodeMultisigOperatorshipTransferredEvent(log *geth.Log) (types.EventMultisigOperatorshipTransferred, error) { - if len(log.Topics) != 1 || log.Topics[0] != MultisigTransferOperatorshipSig { - return types.EventMultisigOperatorshipTransferred{}, fmt.Errorf("event is not OperatorshipTransferred") - } - - newAddresses, newWeights, newThreshold, err := unpackMultisigTransferKeyEvent(log) - if err != nil { - return types.EventMultisigOperatorshipTransferred{}, err - } - - event := types.EventMultisigOperatorshipTransferred{ - NewOperators: slices.Map(newAddresses, func(addr common.Address) types.Address { return types.Address(addr) }), - NewWeights: slices.Map(newWeights, sdk.NewUintFromBigInt), - NewThreshold: sdk.NewUintFromBigInt(newThreshold), - } - - return event, nil -} - -func unpackMultisigTransferKeyEvent(log *geth.Log) ([]common.Address, []*big.Int, *big.Int, error) { - bytesType := funcs.Must(abi.NewType("bytes", "bytes", nil)) - newOperatorsData, err := types.StrictDecode(abi.Arguments{{Type: bytesType}}, log.Data) - if err != nil { - return nil, nil, nil, err - } - - addressesType := funcs.Must(abi.NewType("address[]", "address[]", nil)) - uint256ArrayType := funcs.Must(abi.NewType("uint256[]", "uint256[]", nil)) - uint256Type := funcs.Must(abi.NewType("uint256", "uint256", nil)) - - arguments := abi.Arguments{{Type: addressesType}, {Type: uint256ArrayType}, {Type: uint256Type}} - params, err := types.StrictDecode(arguments, newOperatorsData[0].([]byte)) - if err != nil { - return nil, nil, nil, err - } - - return params[0].([]common.Address), params[1].([]*big.Int), params[2].(*big.Int), nil -} - -// extract receipt processing from ProcessGatewayTxConfirmation, so that it can be used in ProcessGatewayTxsConfirmation -func (mgr Mgr) processGatewayTxLogs(chain nexus.ChainName, gatewayAddress types.Address, logs []*geth.Log) []types.Event { - var events []types.Event - for i, txlog := range logs { - if !bytes.Equal(gatewayAddress.Bytes(), txlog.Address.Bytes()) { - continue - } - - switch txlog.Topics[0] { - case ContractCallSig: - gatewayEvent, err := DecodeEventContractCall(txlog) - if err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "decode event ContractCall failed").Error()) - continue - } - - if err := gatewayEvent.ValidateBasic(); err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event ContractCall").Error()) - continue - } - - events = append(events, types.Event{ - Chain: chain, - TxID: types.Hash(txlog.TxHash), - Index: uint64(i), - Event: &types.Event_ContractCall{ - ContractCall: &gatewayEvent, - }, - }) - case ContractCallWithTokenSig: - gatewayEvent, err := DecodeEventContractCallWithToken(txlog) - if err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "decode event ContractCallWithToken failed").Error()) - continue - } - - if err := gatewayEvent.ValidateBasic(); err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event ContractCallWithToken").Error()) - continue - } - - events = append(events, types.Event{ - Chain: chain, - TxID: types.Hash(txlog.TxHash), - Index: uint64(i), - Event: &types.Event_ContractCallWithToken{ - ContractCallWithToken: &gatewayEvent, - }, - }) - case TokenSentSig: - gatewayEvent, err := DecodeEventTokenSent(txlog) - if err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "decode event TokenSent failed").Error()) - } - - if err := gatewayEvent.ValidateBasic(); err != nil { - mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event TokenSent").Error()) - continue - } - - events = append(events, types.Event{ - Chain: chain, - TxID: types.Hash(txlog.TxHash), - Index: uint64(i), - Event: &types.Event_TokenSent{ - TokenSent: &gatewayEvent, - }, - }) - default: - } - } - - return events -} - // isParticipantOf checks if the validator is in the poll participants list func (mgr Mgr) isParticipantOf(participants []sdk.ValAddress) bool { return slices.Any(participants, func(v sdk.ValAddress) bool { return v.Equals(mgr.validator) }) diff --git a/vald/evm/evm_test.go b/vald/evm/evm_test.go index 034802b60..960740216 100644 --- a/vald/evm/evm_test.go +++ b/vald/evm/evm_test.go @@ -8,169 +8,21 @@ import ( "strings" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" geth "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" - mock2 "github.com/axelarnetwork/axelar-core/sdk-utils/broadcast/mock" - "github.com/axelarnetwork/axelar-core/testutils" "github.com/axelarnetwork/axelar-core/testutils/rand" "github.com/axelarnetwork/axelar-core/vald/evm" evmmock "github.com/axelarnetwork/axelar-core/vald/evm/mock" evmRpc "github.com/axelarnetwork/axelar-core/vald/evm/rpc" "github.com/axelarnetwork/axelar-core/vald/evm/rpc/mock" - "github.com/axelarnetwork/axelar-core/x/evm/exported" - "github.com/axelarnetwork/axelar-core/x/evm/types" - evmtestutils "github.com/axelarnetwork/axelar-core/x/evm/types/testutils" nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" - vote "github.com/axelarnetwork/axelar-core/x/vote/exported" - voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" - "github.com/axelarnetwork/utils/funcs" "github.com/axelarnetwork/utils/monads/results" "github.com/axelarnetwork/utils/slices" . "github.com/axelarnetwork/utils/test" ) -func TestDecodeEventTokenSent(t *testing.T) { - log := &geth.Log{ - Topics: []common.Hash{ - common.HexToHash("0x651d93f66c4329630e8d0f62488eff599e3be484da587335e8dc0fcf46062726"), - common.HexToHash("0x00000000000000000000000068b93045fe7d8794a7caf327e7f855cd6cd03bb8"), - }, - Data: common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000000a657468657265756d2d3200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783538656134313033656439353564434262646338613066456261626133393542366534346431354600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f657468657265756d2d312d7561786c0000000000000000000000000000000000"), - } - - expected := types.EventTokenSent{ - Sender: types.Address(common.HexToAddress("0x68B93045fe7D8794a7cAF327e7f855CD6Cd03BB8")), - DestinationChain: "ethereum-2", - DestinationAddress: "0x58ea4103ed955dCBbdc8a0fEbaba395B6e44d15F", - Symbol: "ethereum-1-uaxl", - Amount: sdk.NewUint(10000000), - } - actual, err := evm.DecodeEventTokenSent(log) - - assert.NoError(t, err) - assert.Equal(t, expected, actual) -} - -func TestDecodeEventContractCall(t *testing.T) { - log := &geth.Log{ - Topics: []common.Hash{ - common.HexToHash("0x30ae6cc78c27e651745bf2ad08a11de83910ac1e347a52f7ac898c0fbef94dae"), - common.HexToHash("0x000000000000000000000000d48e199950589a4336e4dc43bd2c72ba0c0baa86"), - common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e"), - }, - Data: common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000a657468657265756d2d3200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078623938343566393234376138354565353932323733613739363035663334453836303764376537350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066275666665720000000000000000000000000000000000000000000000000000"), - } - - expected := types.EventContractCall{ - Sender: types.Address(common.HexToAddress("0xD48E199950589A4336E4dc43bd2C72Ba0C0baA86")), - DestinationChain: "ethereum-2", - ContractAddress: "0xb9845f9247a85Ee592273a79605f34E8607d7e75", - PayloadHash: types.Hash(common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e")), - } - actual, err := evm.DecodeEventContractCall(log) - - assert.NoError(t, err) - assert.Equal(t, expected, actual) -} - -func TestDecodeEventContractCallWithToken(t *testing.T) { - log := &geth.Log{ - Topics: []common.Hash{ - common.HexToHash("0x7e50569d26be643bda7757722291ec66b1be66d8283474ae3fab5a98f878a7a2"), - common.HexToHash("0x00000000000000000000000068b93045fe7d8794a7caf327e7f855cd6cd03bb8"), - common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e"), - }, - Data: common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000000000000000000008657468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307837366130363034333339313731326245333941333433643166343331363538353466434636446533000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006627566666572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047561786c00000000000000000000000000000000000000000000000000000000"), - } - - expected := types.EventContractCallWithToken{ - Sender: types.Address(common.HexToAddress("0x68B93045fe7D8794a7cAF327e7f855CD6Cd03BB8")), - DestinationChain: "ethereum", - ContractAddress: "0x76a06043391712bE39A343d1f43165854fCF6De3", - PayloadHash: types.Hash(common.HexToHash("0x9fcef596d62dca8e51b6ba3414901947c0e6821d4483b2f3327ce87c2d4e662e")), - Symbol: "uaxl", - Amount: sdk.NewUint(10000000), - } - actual, err := evm.DecodeEventContractCallWithToken(log) - - assert.NoError(t, err) - assert.Equal(t, expected, actual) -} - -func TestDecodeTokenDeployEvent_CorrectData(t *testing.T) { - axelarGateway := common.HexToAddress("0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA") - - tokenDeploySig := evm.ERC20TokenDeploymentSig - expectedAddr := common.HexToAddress("0xE7481ECB61F9C84b91C03414F3D5d48E5436045D") - expectedSymbol := "XPTO" - data := common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e7481ecb61f9c84b91c03414f3d5d48e5436045d00000000000000000000000000000000000000000000000000000000000000045850544f00000000000000000000000000000000000000000000000000000000") - - l := &geth.Log{Address: axelarGateway, Data: data, Topics: []common.Hash{tokenDeploySig}} - - tokenDeployed, err := evm.DecodeERC20TokenDeploymentEvent(l) - assert.NoError(t, err) - assert.Equal(t, expectedSymbol, tokenDeployed.Symbol) - assert.Equal(t, types.Address(expectedAddr), tokenDeployed.TokenAddress) -} - -func TestDecodeErc20TransferEvent_NotErc20Transfer(t *testing.T) { - l := geth.Log{ - Topics: []common.Hash{ - common.BytesToHash(rand.Bytes(common.HashLength)), - common.BytesToHash(common.LeftPadBytes(common.BytesToAddress(rand.Bytes(common.AddressLength)).Bytes(), common.HashLength)), - common.BytesToHash(common.LeftPadBytes(common.BytesToAddress(rand.Bytes(common.AddressLength)).Bytes(), common.HashLength)), - }, - Data: common.LeftPadBytes(big.NewInt(2).Bytes(), common.HashLength), - } - - _, err := evm.DecodeERC20TransferEvent(&l) - assert.Error(t, err) -} - -func TestDecodeErc20TransferEvent_InvalidErc20Transfer(t *testing.T) { - erc20TransferEventSig := common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") - - l := geth.Log{ - Topics: []common.Hash{ - erc20TransferEventSig, - common.BytesToHash(common.LeftPadBytes(common.BytesToAddress(rand.Bytes(common.AddressLength)).Bytes(), common.HashLength)), - }, - Data: common.LeftPadBytes(big.NewInt(2).Bytes(), common.HashLength), - } - - _, err := evm.DecodeERC20TransferEvent(&l) - - assert.Error(t, err) -} - -func TestDecodeErc20TransferEvent_CorrectData(t *testing.T) { - erc20TransferEventSig := common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") - expectedFrom := common.BytesToAddress(rand.Bytes(common.AddressLength)) - expectedTo := common.BytesToAddress(rand.Bytes(common.AddressLength)) - expectedAmount := sdk.NewUint(uint64(rand.I64Between(1, 10000))) - - l := geth.Log{ - Topics: []common.Hash{ - erc20TransferEventSig, - common.BytesToHash(common.LeftPadBytes(expectedFrom.Bytes(), common.HashLength)), - common.BytesToHash(common.LeftPadBytes(expectedTo.Bytes(), common.HashLength)), - }, - Data: common.LeftPadBytes(expectedAmount.BigInt().Bytes(), common.HashLength), - } - - transfer, err := evm.DecodeERC20TransferEvent(&l) - - assert.NoError(t, err) - assert.Equal(t, types.Address(expectedTo), transfer.To) - assert.Equal(t, expectedAmount, transfer.Amount) -} - func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { chain := nexus.ChainName(strings.ToLower(rand.NormalizedStr(5))) tx := geth.NewTransaction(0, common.BytesToAddress(rand.Bytes(common.HashLength)), big.NewInt(rand.PosI64()), uint64(rand.PosI64()), big.NewInt(rand.PosI64()), rand.Bytes(int(rand.I64Between(100, 1000)))) @@ -293,588 +145,6 @@ func TestMgr_GetTxReceiptIfFinalized(t *testing.T) { Run(t, 5) } -func TestMgr_ProccessDepositConfirmation(t *testing.T) { - var ( - mgr *evm.Mgr - receipt *geth.Receipt - tokenAddr types.Address - depositAddr types.Address - amount sdk.Uint - evmMap map[string]evmRpc.Client - rpc *mock.ClientMock - valAddr sdk.ValAddress - - votes []*types.VoteEvents - err error - ) - - givenDeposit := Given("a deposit has been made", func() { - amount = sdk.NewUint(uint64(rand.PosI64())) - tokenAddr = evmtestutils.RandomAddress() - depositAddr = evmtestutils.RandomAddress() - randomTokenDeposit := &geth.Log{ - Address: common.Address(evmtestutils.RandomAddress()), - Topics: []common.Hash{ - evm.ERC20TransferSig, - padToHash(evmtestutils.RandomAddress()), - padToHash(depositAddr), - }, - Data: padToHash(big.NewInt(rand.PosI64())).Bytes(), - } - randomEvent := &geth.Log{ - Address: common.Address(tokenAddr), - Topics: []common.Hash{ - common.Hash(evmtestutils.RandomHash()), - padToHash(evmtestutils.RandomAddress()), - padToHash(depositAddr), - }, - Data: padToHash(big.NewInt(rand.PosI64())).Bytes(), - } - - invalidDeposit := &geth.Log{ - Address: common.Address(tokenAddr), - Topics: []common.Hash{ - evm.ERC20TransferSig, - padToHash(evmtestutils.RandomAddress()), - }, - Data: padToHash(big.NewInt(rand.PosI64())).Bytes(), - } - - zeroAmountDeposit := &geth.Log{ - Address: common.Address(tokenAddr), - Topics: []common.Hash{ - evm.ERC20TransferSig, - padToHash(evmtestutils.RandomAddress()), - padToHash(depositAddr), - }, - Data: padToHash(big.NewInt(0)).Bytes(), - } - - validDeposit := &geth.Log{ - Address: common.Address(tokenAddr), - Topics: []common.Hash{ - evm.ERC20TransferSig, - padToHash(evmtestutils.RandomAddress()), - padToHash(depositAddr), - }, - Data: padToHash(amount.BigInt()).Bytes(), - } - - receipt = &geth.Receipt{ - TxHash: common.Hash(evmtestutils.RandomHash()), - BlockNumber: big.NewInt(rand.PosI64()), - Logs: []*geth.Log{randomTokenDeposit, validDeposit, randomEvent, invalidDeposit, zeroAmountDeposit}, - Status: 1, - } - }) - - confirmingDeposit := When("confirming the existing deposit", func() { - event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipt.TxHash) - evmMap[strings.ToLower(event.Chain.String())] = rpc - event.DepositAddress = depositAddr - event.TokenAddress = tokenAddr - event.Participants = append(event.Participants, valAddr) - - err = mgr.ProcessDepositConfirmation(&event) - }) - - reject := Then("reject the confirmation", func(t *testing.T) { - assert.Len(t, votes, 1) - assert.Len(t, votes[0].Events, 0) - }) - - noError := Then("return no error", func(t *testing.T) { - assert.NoError(t, err) - }) - - Given("an evm manager", func() { - votes = []*types.VoteEvents{} - evmMap = make(map[string]evmRpc.Client, 1) - - broadcaster := &mock2.BroadcasterMock{ - BroadcastFunc: func(_ context.Context, msgs ...sdk.Msg) (*sdk.TxResponse, error) { - for _, msg := range msgs { - votes = append(votes, msg.(*voteTypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents)) - } - return nil, nil - }, - } - - valAddr = rand.ValAddr() - mgr = evm.NewMgr(evmMap, broadcaster, valAddr, rand.AccAddr(), &evmmock.LatestFinalizedBlockCacheMock{ - GetFunc: func(_ nexus.ChainName) *big.Int { return big.NewInt(0) }, - SetFunc: func(_ nexus.ChainName, _ *big.Int) {}, - }) - }). - Given("an evm rpc client", func() { - rpc = &mock.ClientMock{ - HeaderByNumberFunc: func(context.Context, *big.Int) (*evmRpc.Header, error) { - return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil - }, - TransactionReceiptFunc: func(_ context.Context, txID common.Hash) (*geth.Receipt, error) { - if txID != receipt.TxHash { - return nil, ethereum.NotFound - } - return receipt, nil - }, - LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { - return receipt.BlockNumber, nil - }, - } - }). - Branch( - Given("no deposit has been made", func() { - rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { - return nil, ethereum.NotFound - } - }). - When("confirming a random deposit on the correct chain", func() { - event := evmtestutils.RandomConfirmDepositStarted() - event.Participants = append(event.Participants, valAddr) - - evmMap[strings.ToLower(event.Chain.String())] = rpc - err = mgr.ProcessDepositConfirmation(&event) - }). - Then2(noError). - Then2(reject), - - givenDeposit. - When("confirming the deposit on unsupported chain", func() { - event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipt.TxHash) - event.DepositAddress = depositAddr - event.TokenAddress = tokenAddr - event.Participants = append(event.Participants, valAddr) - - err = mgr.ProcessDepositConfirmation(&event) - - }). - Then("return error", func(t *testing.T) { - assert.Error(t, err) - }), - - givenDeposit. - Given("confirmation height is not reached yet", func() { - rpc.LatestFinalizedBlockNumberFunc = func(ctx context.Context, confirmations uint64) (*big.Int, error) { - return sdk.NewIntFromBigInt(receipt.BlockNumber).SubRaw(int64(confirmations)).BigInt(), nil - } - }). - When2(confirmingDeposit). - Then2(noError). - Then2(reject), - - givenDeposit. - When2(confirmingDeposit). - Then2(noError). - Then("accept the confirmation", func(t *testing.T) { - assert.Len(t, votes, 1) - assert.Len(t, votes[0].Events, 1) - transferEvent, ok := votes[0].Events[0].Event.(*types.Event_Transfer) - assert.True(t, ok) - - assert.Equal(t, depositAddr, transferEvent.Transfer.To) - assert.True(t, transferEvent.Transfer.Amount.Equal(amount)) - }), - - givenDeposit. - When("confirming event with wrong tx ID", func() { - event := evmtestutils.RandomConfirmDepositStarted() - evmMap[strings.ToLower(event.Chain.String())] = rpc - event.DepositAddress = depositAddr - event.TokenAddress = tokenAddr - event.Participants = append(event.Participants, valAddr) - - err = mgr.ProcessDepositConfirmation(&event) - }). - Then2(noError). - Then2(reject), - - givenDeposit. - When("confirming event with wrong token address", func() { - event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipt.TxHash) - evmMap[strings.ToLower(event.Chain.String())] = rpc - event.DepositAddress = depositAddr - event.Participants = append(event.Participants, valAddr) - - err = mgr.ProcessDepositConfirmation(&event) - }). - Then2(noError). - Then2(reject), - - givenDeposit. - When("confirming event with wrong deposit address", func() { - event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipt.TxHash) - evmMap[strings.ToLower(event.Chain.String())] = rpc - event.TokenAddress = tokenAddr - event.Participants = append(event.Participants, valAddr) - - err = mgr.ProcessDepositConfirmation(&event) - }). - Then2(noError). - Then2(reject), - - givenDeposit. - When("confirming a deposit without being a participant", func() { - event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipt.TxHash) - evmMap[strings.ToLower(event.Chain.String())] = rpc - event.DepositAddress = depositAddr - event.TokenAddress = tokenAddr - - err = mgr.ProcessDepositConfirmation(&event) - }). - Then2(noError). - Then("do nothing", func(t *testing.T) { - assert.Len(t, votes, 0) - }), - - givenDeposit. - Given("multiple deposits in a single tx", func() { - additionalAmount := sdk.NewUint(uint64(rand.PosI64())) - amount = amount.Add(additionalAmount) - additionalDeposit := &geth.Log{ - Address: common.Address(tokenAddr), - Topics: []common.Hash{ - evm.ERC20TransferSig, - padToHash(evmtestutils.RandomAddress()), - padToHash(depositAddr), - }, - Data: padToHash(additionalAmount.BigInt()).Bytes(), - } - receipt.Logs = append(receipt.Logs, additionalDeposit) - }). - When("confirming the deposits", func() { - event := evmtestutils.RandomConfirmDepositStarted() - event.TxID = types.Hash(receipt.TxHash) - evmMap[strings.ToLower(event.Chain.String())] = rpc - event.DepositAddress = depositAddr - event.TokenAddress = tokenAddr - event.Participants = append(event.Participants, valAddr) - - err = mgr.ProcessDepositConfirmation(&event) - }). - Then2(noError). - Then("vote for all deposits in the same tx", func(t *testing.T) { - assert.Len(t, votes, 1) - assert.Len(t, votes[0].Events, 2) - - actualAmount := sdk.ZeroUint() - for _, event := range votes[0].Events { - transferEvent, ok := event.Event.(*types.Event_Transfer) - assert.True(t, ok) - assert.Equal(t, depositAddr, transferEvent.Transfer.To) - - actualAmount = actualAmount.Add(transferEvent.Transfer.Amount) - } - - assert.True(t, actualAmount.Equal(amount)) - }), - ).Run(t, 20) - -} - -func TestMgr_ProccessTokenConfirmation(t *testing.T) { - var ( - mgr *evm.Mgr - event *types.ConfirmTokenStarted - rpc *mock.ClientMock - broadcaster *mock2.BroadcasterMock - gatewayAddrBytes []byte - valAddr sdk.ValAddress - ) - setup := func() { - pollID := vote.PollID(rand.I64Between(10, 100)) - - gatewayAddrBytes = rand.Bytes(common.AddressLength) - tokenAddrBytes := rand.Bytes(common.AddressLength) - blockNumber := rand.PInt64Gen().Where(func(i int64) bool { return i != 0 }).Next() // restrict to int64 so the block number in the receipt doesn't overflow - confHeight := rand.I64Between(0, blockNumber-1) - - symbol := rand.Denom(5, 20) - valAddr = rand.ValAddr() - event = &types.ConfirmTokenStarted{ - TxID: types.Hash(common.BytesToHash(rand.Bytes(common.HashLength))), - Chain: "Ethereum", - GatewayAddress: types.Address(common.BytesToAddress(gatewayAddrBytes)), - TokenAddress: types.Address(common.BytesToAddress(tokenAddrBytes)), - TokenDetails: types.TokenDetails{Symbol: symbol}, - ConfirmationHeight: uint64(confHeight), - PollParticipants: vote.PollParticipants{ - PollID: pollID, - Participants: []sdk.ValAddress{valAddr}, - }, - } - - tx := geth.NewTransaction(0, common.BytesToAddress(rand.Bytes(common.HashLength)), big.NewInt(0), 21000, big.NewInt(1), []byte{}) - receipt := &geth.Receipt{ - TxHash: tx.Hash(), - BlockNumber: big.NewInt(rand.I64Between(0, blockNumber-confHeight)), - Logs: createTokenLogs( - symbol, - common.BytesToAddress(gatewayAddrBytes), - common.BytesToAddress(tokenAddrBytes), - evm.ERC20TokenDeploymentSig, - true, - ), - Status: 1, - } - rpc = &mock.ClientMock{ - HeaderByNumberFunc: func(ctx context.Context, number *big.Int) (*evmRpc.Header, error) { - return &evmRpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil - }, - TransactionReceiptFunc: func(context.Context, common.Hash) (*geth.Receipt, error) { - return receipt, nil - }, - LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { - return receipt.BlockNumber, nil - }, - } - broadcaster = &mock2.BroadcasterMock{ - BroadcastFunc: func(context.Context, ...sdk.Msg) (*sdk.TxResponse, error) { return nil, nil }, - } - evmMap := make(map[string]evmRpc.Client) - evmMap["ethereum"] = rpc - mgr = evm.NewMgr(evmMap, broadcaster, valAddr, rand.AccAddr(), &evmmock.LatestFinalizedBlockCacheMock{ - GetFunc: func(_ nexus.ChainName) *big.Int { return big.NewInt(0) }, - SetFunc: func(_ nexus.ChainName, _ *big.Int) {}, - }) - } - - repeats := 20 - t.Run("happy path", testutils.Func(func(t *testing.T) { - setup() - - err := mgr.ProcessTokenConfirmation(event) - - assert.NoError(t, err) - assert.Len(t, broadcaster.BroadcastCalls(), 1) - - msg := broadcaster.BroadcastCalls()[0].Msgs[0] - actualVoteEvents := msg.(*voteTypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) - assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) - assert.Len(t, actualVoteEvents.Events, 1) - }).Repeat(repeats)) - - t.Run("no tx receipt", testutils.Func(func(t *testing.T) { - setup() - rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return nil, ethereum.NotFound } - - err := mgr.ProcessTokenConfirmation(event) - - assert.NoError(t, err) - assert.Len(t, broadcaster.BroadcastCalls(), 1) - - msg := broadcaster.BroadcastCalls()[0].Msgs[0] - actualVoteEvents := msg.(*voteTypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) - assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) - assert.Len(t, actualVoteEvents.Events, 0) - }).Repeat(repeats)) - - t.Run("no deploy event", testutils.Func(func(t *testing.T) { - setup() - receipt, _ := rpc.TransactionReceipt(context.Background(), common.Hash{}) - var correctLogIdx int - for i, l := range receipt.Logs { - if l.Address == common.BytesToAddress(gatewayAddrBytes) { - correctLogIdx = i - break - } - } - // remove the deploy event - receipt.Logs = append(receipt.Logs[:correctLogIdx], receipt.Logs[correctLogIdx+1:]...) - rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } - - err := mgr.ProcessTokenConfirmation(event) - - assert.NoError(t, err) - assert.Len(t, broadcaster.BroadcastCalls(), 1) - - msg := broadcaster.BroadcastCalls()[0].Msgs[0] - actualVoteEvents := msg.(*voteTypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) - assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) - assert.Len(t, actualVoteEvents.Events, 0) - - }).Repeat(repeats)) - - t.Run("wrong deploy event", testutils.Func(func(t *testing.T) { - setup() - receipt, _ := rpc.TransactionReceipt(context.Background(), common.Hash{}) - for _, l := range receipt.Logs { - if l.Address == common.BytesToAddress(gatewayAddrBytes) { - l.Data = rand.Bytes(int(rand.I64Between(0, 1000))) - break - } - } - rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } - - err := mgr.ProcessTokenConfirmation(event) - - assert.NoError(t, err) - assert.Len(t, broadcaster.BroadcastCalls(), 1) - - msg := broadcaster.BroadcastCalls()[0].Msgs[0] - actualVoteEvents := msg.(*voteTypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) - assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) - assert.Len(t, actualVoteEvents.Events, 0) - }).Repeat(repeats)) -} - -func TestMgr_ProcessTransferKeyConfirmation(t *testing.T) { - var ( - mgr *evm.Mgr - event *types.ConfirmKeyTransferStarted - rpc *mock.ClientMock - broadcaster *mock2.BroadcasterMock - txID types.Hash - gatewayAddress types.Address - pollID vote.PollID - txReceipt *geth.Receipt - valAddr sdk.ValAddress - ) - - givenEvmMgr := Given("EVM mgr", func() { - rpc = &mock.ClientMock{} - broadcaster = &mock2.BroadcasterMock{ - BroadcastFunc: func(ctx context.Context, msgs ...sdk.Msg) (*sdk.TxResponse, error) { return nil, nil }, - } - evmMap := make(map[string]evmRpc.Client) - evmMap["ethereum"] = rpc - valAddr = rand.ValAddr() - mgr = evm.NewMgr(evmMap, broadcaster, valAddr, rand.AccAddr(), &evmmock.LatestFinalizedBlockCacheMock{ - GetFunc: func(_ nexus.ChainName) *big.Int { return big.NewInt(0) }, - SetFunc: func(_ nexus.ChainName, _ *big.Int) {}, - }) - }) - - givenTxReceiptAndBlockAreFound := Given("tx receipt and block can be found", func() { - tx := geth.NewTransaction(0, common.BytesToAddress(rand.Bytes(common.HashLength)), big.NewInt(0), 21000, big.NewInt(1), []byte{}) - blockNumber := uint64(rand.I64Between(1, 1000)) - - txID = types.Hash(tx.Hash()) - txReceipt = &geth.Receipt{ - TxHash: common.Hash(txID), - BlockNumber: big.NewInt(rand.I64Between(0, int64(blockNumber-types.DefaultParams()[0].ConfirmationHeight+2))), - Logs: []*geth.Log{}, - Status: 1, - } - - rpc.TransactionReceiptFunc = func(ctx context.Context, txHash common.Hash) (*geth.Receipt, error) { - if txHash == common.Hash(txID) { - return txReceipt, nil - } - - return nil, fmt.Errorf("not found") - } - rpc.HeaderByNumberFunc = func(ctx context.Context, number *big.Int) (*evmRpc.Header, error) { - if number.Cmp(txReceipt.BlockNumber) == 0 { - number := hexutil.Big(*big.NewInt(int64(blockNumber))) - return &evmRpc.Header{Number: &number, Transactions: []common.Hash{txReceipt.TxHash}}, nil - } - - return nil, fmt.Errorf("not found") - } - rpc.LatestFinalizedBlockNumberFunc = func(ctx context.Context, confirmations uint64) (*big.Int, error) { - return txReceipt.BlockNumber, nil - } - }) - - givenEventConfirmKeyTransfer := Given("event confirm key transfer", func() { - gatewayAddress = types.Address(common.BytesToAddress(rand.Bytes(common.AddressLength))) - pollID = vote.PollID(rand.PosI64()) - event = types.NewConfirmKeyTransferStarted( - exported.Ethereum.Name, - txID, - gatewayAddress, - types.DefaultParams()[0].ConfirmationHeight, - vote.PollParticipants{ - PollID: pollID, - Participants: []sdk.ValAddress{valAddr}, - }, - ) - }) - - assertAndGetVoteEvents := func(t *testing.T, isEmpty bool) *types.VoteEvents { - assert.Len(t, broadcaster.BroadcastCalls(), 1) - assert.Len(t, broadcaster.BroadcastCalls()[0].Msgs, 1) - - voteEvents := broadcaster.BroadcastCalls()[0].Msgs[0].(*voteTypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) - if isEmpty { - assert.Empty(t, voteEvents.Events) - } else { - assert.Len(t, voteEvents.Events, 1) - } - - return voteEvents - } - - thenShouldVoteNoEvent := Then("should vote no event", func(t *testing.T) { - err := mgr.ProcessTransferKeyConfirmation(event) - assert.NoError(t, err) - - assertAndGetVoteEvents(t, true) - }) - - givenEvmMgr. - Given2(givenTxReceiptAndBlockAreFound). - Given2(givenEventConfirmKeyTransfer). - Branch( - When("is not operatorship transferred event", func() { - txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ - Address: common.Address(gatewayAddress), - Topics: []common.Hash{common.BytesToHash(rand.Bytes(common.HashLength))}, - }) - }). - Then2(thenShouldVoteNoEvent), - - When("is not emitted from the gateway", func() { - txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ - Address: common.BytesToAddress(rand.Bytes(common.AddressLength)), - Topics: []common.Hash{evm.MultisigTransferOperatorshipSig}, - }) - }). - Then2(thenShouldVoteNoEvent), - - When("is invalid operatorship transferred event", func() { - txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ - Address: common.Address(gatewayAddress), - Topics: []common.Hash{evm.MultisigTransferOperatorshipSig}, - Data: rand.Bytes(int(rand.I64Between(0, 1000))), - }) - }). - Then2(thenShouldVoteNoEvent), - - When("is valid operatorship transferred event", func() { - newOperatorsData := common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000800000000000000000000000019cc2044857d23129a29f763d0338da837ce35f60000000000000000000000002ab6fa7de5e9e9423125a4246e4de1b9c755607400000000000000000000000037cc4b7e8f9f505ca8126db8a9d070566ed5dae70000000000000000000000003e56f0d4497ac44993d9ea272d4707f8be6b42a6000000000000000000000000462b96f617d5d92f63f9949c6f4626623ea73fa400000000000000000000000068b93045fe7d8794a7caf327e7f855cd6cd03bb80000000000000000000000009e77c30badbbc412a0c20c6ce43b671c6f103434000000000000000000000000c1c0c8d2131cc866834c6382096eadfef1af2f52000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000005") - txReceipt.Logs = append(txReceipt.Logs, &geth.Log{}) - txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ - Address: common.Address(gatewayAddress), - Topics: []common.Hash{evm.MultisigTransferOperatorshipSig}, - Data: funcs.Must(abi.Arguments{{Type: funcs.Must(abi.NewType("bytes", "bytes", nil))}}.Pack(newOperatorsData)), - }) - }). - Then("should vote for the correct event", func(t *testing.T) { - err := mgr.ProcessTransferKeyConfirmation(event) - assert.NoError(t, err) - - actual := assertAndGetVoteEvents(t, false) - assert.Equal(t, exported.Ethereum.Name, actual.Chain) - assert.Equal(t, exported.Ethereum.Name, actual.Events[0].Chain) - assert.Equal(t, txID, actual.Events[0].TxID) - assert.EqualValues(t, 1, actual.Events[0].Index) - assert.IsType(t, &types.Event_MultisigOperatorshipTransferred{}, actual.Events[0].Event) - - actualEvent := actual.Events[0].Event.(*types.Event_MultisigOperatorshipTransferred) - assert.Len(t, actualEvent.MultisigOperatorshipTransferred.NewOperators, 8) - assert.Len(t, actualEvent.MultisigOperatorshipTransferred.NewWeights, 8) - assert.EqualValues(t, 30, actualEvent.MultisigOperatorshipTransferred.NewThreshold.BigInt().Int64()) - }), - ). - Run(t, 5) -} - func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { chain := nexus.ChainName(strings.ToLower(rand.NormalizedStr(5))) txHashes := slices.Expand2(func() common.Hash { return common.BytesToHash(rand.Bytes(common.HashLength)) }, 100) @@ -976,52 +246,3 @@ func TestMgr_GetTxReceiptsIfFinalized(t *testing.T) { ). Run(t, 5) } - -func createTokenLogs(denom string, gateway, tokenAddr common.Address, deploySig common.Hash, hasCorrectLog bool) []*geth.Log { - numLogs := rand.I64Between(1, 100) - correctPos := rand.I64Between(0, numLogs) - var logs []*geth.Log - - for i := int64(0); i < numLogs; i++ { - stringType, err := abi.NewType("string", "string", nil) - if err != nil { - panic(err) - } - addressType, err := abi.NewType("address", "address", nil) - if err != nil { - panic(err) - } - args := abi.Arguments{{Type: stringType}, {Type: addressType}} - - switch { - case hasCorrectLog && i == correctPos: - data, err := args.Pack(denom, tokenAddr) - if err != nil { - panic(err) - } - logs = append(logs, &geth.Log{Address: gateway, Data: data, Topics: []common.Hash{deploySig}}) - default: - randDenom := rand.StrBetween(5, 20) - randAddr := common.BytesToAddress(rand.Bytes(common.AddressLength)) - randData, err := args.Pack(randDenom, randAddr) - if err != nil { - panic(err) - } - logs = append(logs, &geth.Log{ - Address: common.BytesToAddress(rand.Bytes(common.AddressLength)), - Data: randData, - Topics: []common.Hash{common.BytesToHash(rand.Bytes(common.HashLength))}, - }) - } - } - - return logs -} - -type byter interface { - Bytes() []byte -} - -func padToHash[T byter](x T) common.Hash { - return common.BytesToHash(common.LeftPadBytes(x.Bytes(), common.HashLength)) -} diff --git a/vald/evm/gateway_tx_confirmation.go b/vald/evm/gateway_tx_confirmation.go new file mode 100644 index 000000000..cfb296d62 --- /dev/null +++ b/vald/evm/gateway_tx_confirmation.go @@ -0,0 +1,115 @@ +package evm + +import ( + "bytes" + "context" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + + "github.com/axelarnetwork/axelar-core/x/evm/types" + nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" + voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" +) + +// ProcessGatewayTxConfirmation votes on the correctness of an EVM chain gateway's transactions +func (mgr Mgr) ProcessGatewayTxConfirmation(event *types.ConfirmGatewayTxStarted) error { + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring gateway tx confirmation poll: not a participant") + return nil + } + + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) + + return err + } + + events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, txReceipt.Logs) + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err +} + +// extract receipt processing from ProcessGatewayTxConfirmation, so that it can be used in ProcessGatewayTxsConfirmation +func (mgr Mgr) processGatewayTxLogs(chain nexus.ChainName, gatewayAddress types.Address, logs []*geth.Log) []types.Event { + var events []types.Event + for i, txlog := range logs { + if !bytes.Equal(gatewayAddress.Bytes(), txlog.Address.Bytes()) { + continue + } + + switch txlog.Topics[0] { + case ContractCallSig: + gatewayEvent, err := DecodeEventContractCall(txlog) + if err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "decode event ContractCall failed").Error()) + continue + } + + if err := gatewayEvent.ValidateBasic(); err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event ContractCall").Error()) + continue + } + + events = append(events, types.Event{ + Chain: chain, + TxID: types.Hash(txlog.TxHash), + Index: uint64(i), + Event: &types.Event_ContractCall{ + ContractCall: &gatewayEvent, + }, + }) + case ContractCallWithTokenSig: + gatewayEvent, err := DecodeEventContractCallWithToken(txlog) + if err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "decode event ContractCallWithToken failed").Error()) + continue + } + + if err := gatewayEvent.ValidateBasic(); err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event ContractCallWithToken").Error()) + continue + } + + events = append(events, types.Event{ + Chain: chain, + TxID: types.Hash(txlog.TxHash), + Index: uint64(i), + Event: &types.Event_ContractCallWithToken{ + ContractCallWithToken: &gatewayEvent, + }, + }) + case TokenSentSig: + gatewayEvent, err := DecodeEventTokenSent(txlog) + if err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "decode event TokenSent failed").Error()) + } + + if err := gatewayEvent.ValidateBasic(); err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event TokenSent").Error()) + continue + } + + events = append(events, types.Event{ + Chain: chain, + TxID: types.Hash(txlog.TxHash), + Index: uint64(i), + Event: &types.Event_TokenSent{ + TokenSent: &gatewayEvent, + }, + }) + default: + } + } + + return events +} diff --git a/vald/evm/gateway_txs_confirmation.go b/vald/evm/gateway_txs_confirmation.go new file mode 100644 index 000000000..2f79e4741 --- /dev/null +++ b/vald/evm/gateway_txs_confirmation.go @@ -0,0 +1,65 @@ +package evm + +import ( + "context" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + + "github.com/axelarnetwork/axelar-core/x/evm/types" + vote "github.com/axelarnetwork/axelar-core/x/vote/exported" + votetypes "github.com/axelarnetwork/axelar-core/x/vote/types" + "github.com/axelarnetwork/utils/slices" +) + +// ProcessGatewayTxsConfirmation votes on the correctness of an EVM chain multiple gateway transactions +func (mgr Mgr) ProcessGatewayTxsConfirmation(event *types.ConfirmGatewayTxsStarted) error { + if !mgr.isParticipantOf(event.Participants) { + pollIDs := slices.Map(event.PollMappings, func(m types.PollMapping) vote.PollID { return m.PollID }) + mgr.logger("poll_ids", pollIDs).Debug("ignoring gateway txs confirmation poll: not a participant") + return nil + } + + txIDs := slices.Map(event.PollMappings, func(poll types.PollMapping) common.Hash { return common.Hash(poll.TxID) }) + txReceipts, err := mgr.GetTxReceiptsIfFinalized(event.Chain, txIDs, event.ConfirmationHeight) + if err != nil { + return err + } + + var votes []sdk.Msg + for i, result := range txReceipts { + pollID := event.PollMappings[i].PollID + txID := event.PollMappings[i].TxID + + logger := mgr.logger("chain", event.Chain, "poll_id", pollID.String(), "tx_id", txID.Hex()) + + // only broadcast empty votes if the tx is not found or not finalized + switch result.Err() { + case nil: + events := mgr.processGatewayTxLogs(event.Chain, event.GatewayAddress, result.Ok().Logs) + logger.Infof("broadcasting vote %v", events) + votes = append(votes, votetypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain, events...))) + case ErrNotFinalized: + logger.Debug(fmt.Sprintf("transaction %s not finalized", txID.Hex())) + logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) + votes = append(votes, votetypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) + case ErrTxFailed: + logger.Debug(fmt.Sprintf("transaction %s failed", txID.Hex())) + logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) + votes = append(votes, votetypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) + case ethereum.NotFound: + logger.Debug(fmt.Sprintf("transaction receipt %s not found", txID.Hex())) + logger.Infof("broadcasting empty vote due to error: %s", result.Err().Error()) + votes = append(votes, votetypes.NewVoteRequest(mgr.proxy, pollID, types.NewVoteEvents(event.Chain))) + default: + logger.Errorf("failed to get tx receipt: %s", result.Err().Error()) + } + + } + + _, err = mgr.broadcaster.Broadcast(context.TODO(), votes...) + + return err +} diff --git a/vald/evm/key_transfer_confirmation.go b/vald/evm/key_transfer_confirmation.go new file mode 100644 index 000000000..ec684f727 --- /dev/null +++ b/vald/evm/key_transfer_confirmation.go @@ -0,0 +1,69 @@ +package evm + +import ( + "context" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" + + "github.com/axelarnetwork/axelar-core/x/evm/types" + voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" +) + +// ProcessTransferKeyConfirmation votes on the correctness of an EVM chain key transfer +func (mgr Mgr) ProcessTransferKeyConfirmation(event *types.ConfirmKeyTransferStarted) error { + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring key transfer confirmation poll: not a participant") + return nil + } + + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) + + return err + } + + var events []types.Event + for i := len(txReceipt.Logs) - 1; i >= 0; i-- { + txlog := txReceipt.Logs[i] + + if txlog.Topics[0] != MultisigTransferOperatorshipSig { + continue + } + + // Event is not emitted by the axelar gateway + if txlog.Address != common.Address(event.GatewayAddress) { + continue + } + + transferOperatorshipEvent, err := DecodeMultisigOperatorshipTransferredEvent(txlog) + if err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "failed decoding operatorship transferred event").Error()) + continue + } + + if err := transferOperatorshipEvent.ValidateBasic(); err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event MultisigTransferOperatorship").Error()) + continue + } + + events = append(events, types.Event{ + Chain: event.Chain, + TxID: event.TxID, + Index: uint64(i), + Event: &types.Event_MultisigOperatorshipTransferred{ + MultisigOperatorshipTransferred: &transferOperatorshipEvent, + }}) + break + } + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err +} diff --git a/vald/evm/key_transfer_confirmation_test.go b/vald/evm/key_transfer_confirmation_test.go new file mode 100644 index 000000000..0ff31be06 --- /dev/null +++ b/vald/evm/key_transfer_confirmation_test.go @@ -0,0 +1,182 @@ +package evm_test + +import ( + "context" + "fmt" + "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + geth "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + + broadcastmock "github.com/axelarnetwork/axelar-core/sdk-utils/broadcast/mock" + "github.com/axelarnetwork/axelar-core/testutils/rand" + "github.com/axelarnetwork/axelar-core/vald/evm" + evmmock "github.com/axelarnetwork/axelar-core/vald/evm/mock" + evmrpc "github.com/axelarnetwork/axelar-core/vald/evm/rpc" + "github.com/axelarnetwork/axelar-core/vald/evm/rpc/mock" + "github.com/axelarnetwork/axelar-core/x/evm/exported" + "github.com/axelarnetwork/axelar-core/x/evm/types" + nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" + vote "github.com/axelarnetwork/axelar-core/x/vote/exported" + votetypes "github.com/axelarnetwork/axelar-core/x/vote/types" + "github.com/axelarnetwork/utils/funcs" + . "github.com/axelarnetwork/utils/test" +) + +func TestMgr_ProcessTransferKeyConfirmation(t *testing.T) { + var ( + mgr *evm.Mgr + event *types.ConfirmKeyTransferStarted + rpc *mock.ClientMock + broadcaster *broadcastmock.BroadcasterMock + txID types.Hash + gatewayAddress types.Address + pollID vote.PollID + txReceipt *geth.Receipt + valAddr sdk.ValAddress + ) + + givenEvmMgr := Given("EVM mgr", func() { + rpc = &mock.ClientMock{} + broadcaster = &broadcastmock.BroadcasterMock{ + BroadcastFunc: func(ctx context.Context, msgs ...sdk.Msg) (*sdk.TxResponse, error) { return nil, nil }, + } + evmMap := make(map[string]evmrpc.Client) + evmMap["ethereum"] = rpc + valAddr = rand.ValAddr() + mgr = evm.NewMgr(evmMap, broadcaster, valAddr, rand.AccAddr(), &evmmock.LatestFinalizedBlockCacheMock{ + GetFunc: func(_ nexus.ChainName) *big.Int { return big.NewInt(0) }, + SetFunc: func(_ nexus.ChainName, _ *big.Int) {}, + }) + }) + + givenTxReceiptAndBlockAreFound := Given("tx receipt and block can be found", func() { + tx := geth.NewTransaction(0, common.BytesToAddress(rand.Bytes(common.HashLength)), big.NewInt(0), 21000, big.NewInt(1), []byte{}) + blockNumber := uint64(rand.I64Between(1, 1000)) + + txID = types.Hash(tx.Hash()) + txReceipt = &geth.Receipt{ + TxHash: common.Hash(txID), + BlockNumber: big.NewInt(rand.I64Between(0, int64(blockNumber-types.DefaultParams()[0].ConfirmationHeight+2))), + Logs: []*geth.Log{}, + Status: 1, + } + + rpc.TransactionReceiptFunc = func(ctx context.Context, txHash common.Hash) (*geth.Receipt, error) { + if txHash == common.Hash(txID) { + return txReceipt, nil + } + + return nil, fmt.Errorf("not found") + } + rpc.HeaderByNumberFunc = func(ctx context.Context, number *big.Int) (*evmrpc.Header, error) { + if number.Cmp(txReceipt.BlockNumber) == 0 { + number := hexutil.Big(*big.NewInt(int64(blockNumber))) + return &evmrpc.Header{Number: &number, Transactions: []common.Hash{txReceipt.TxHash}}, nil + } + + return nil, fmt.Errorf("not found") + } + rpc.LatestFinalizedBlockNumberFunc = func(ctx context.Context, confirmations uint64) (*big.Int, error) { + return txReceipt.BlockNumber, nil + } + }) + + givenEventConfirmKeyTransfer := Given("event confirm key transfer", func() { + gatewayAddress = types.Address(common.BytesToAddress(rand.Bytes(common.AddressLength))) + pollID = vote.PollID(rand.PosI64()) + event = types.NewConfirmKeyTransferStarted( + exported.Ethereum.Name, + txID, + gatewayAddress, + types.DefaultParams()[0].ConfirmationHeight, + vote.PollParticipants{ + PollID: pollID, + Participants: []sdk.ValAddress{valAddr}, + }, + ) + }) + + assertAndGetVoteEvents := func(t *testing.T, isEmpty bool) *types.VoteEvents { + assert.Len(t, broadcaster.BroadcastCalls(), 1) + assert.Len(t, broadcaster.BroadcastCalls()[0].Msgs, 1) + + voteEvents := broadcaster.BroadcastCalls()[0].Msgs[0].(*votetypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) + if isEmpty { + assert.Empty(t, voteEvents.Events) + } else { + assert.Len(t, voteEvents.Events, 1) + } + + return voteEvents + } + + thenShouldVoteNoEvent := Then("should vote no event", func(t *testing.T) { + err := mgr.ProcessTransferKeyConfirmation(event) + assert.NoError(t, err) + + assertAndGetVoteEvents(t, true) + }) + + givenEvmMgr. + Given2(givenTxReceiptAndBlockAreFound). + Given2(givenEventConfirmKeyTransfer). + Branch( + When("is not operatorship transferred event", func() { + txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ + Address: common.Address(gatewayAddress), + Topics: []common.Hash{common.BytesToHash(rand.Bytes(common.HashLength))}, + }) + }). + Then2(thenShouldVoteNoEvent), + + When("is not emitted from the gateway", func() { + txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ + Address: common.BytesToAddress(rand.Bytes(common.AddressLength)), + Topics: []common.Hash{evm.MultisigTransferOperatorshipSig}, + }) + }). + Then2(thenShouldVoteNoEvent), + + When("is invalid operatorship transferred event", func() { + txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ + Address: common.Address(gatewayAddress), + Topics: []common.Hash{evm.MultisigTransferOperatorshipSig}, + Data: rand.Bytes(int(rand.I64Between(0, 1000))), + }) + }). + Then2(thenShouldVoteNoEvent), + + When("is valid operatorship transferred event", func() { + newOperatorsData := common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000800000000000000000000000019cc2044857d23129a29f763d0338da837ce35f60000000000000000000000002ab6fa7de5e9e9423125a4246e4de1b9c755607400000000000000000000000037cc4b7e8f9f505ca8126db8a9d070566ed5dae70000000000000000000000003e56f0d4497ac44993d9ea272d4707f8be6b42a6000000000000000000000000462b96f617d5d92f63f9949c6f4626623ea73fa400000000000000000000000068b93045fe7d8794a7caf327e7f855cd6cd03bb80000000000000000000000009e77c30badbbc412a0c20c6ce43b671c6f103434000000000000000000000000c1c0c8d2131cc866834c6382096eadfef1af2f52000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000005") + txReceipt.Logs = append(txReceipt.Logs, &geth.Log{}) + txReceipt.Logs = append(txReceipt.Logs, &geth.Log{ + Address: common.Address(gatewayAddress), + Topics: []common.Hash{evm.MultisigTransferOperatorshipSig}, + Data: funcs.Must(abi.Arguments{{Type: funcs.Must(abi.NewType("bytes", "bytes", nil))}}.Pack(newOperatorsData)), + }) + }). + Then("should vote for the correct event", func(t *testing.T) { + err := mgr.ProcessTransferKeyConfirmation(event) + assert.NoError(t, err) + + actual := assertAndGetVoteEvents(t, false) + assert.Equal(t, exported.Ethereum.Name, actual.Chain) + assert.Equal(t, exported.Ethereum.Name, actual.Events[0].Chain) + assert.Equal(t, txID, actual.Events[0].TxID) + assert.EqualValues(t, 1, actual.Events[0].Index) + assert.IsType(t, &types.Event_MultisigOperatorshipTransferred{}, actual.Events[0].Event) + + actualEvent := actual.Events[0].Event.(*types.Event_MultisigOperatorshipTransferred) + assert.Len(t, actualEvent.MultisigOperatorshipTransferred.NewOperators, 8) + assert.Len(t, actualEvent.MultisigOperatorshipTransferred.NewWeights, 8) + assert.EqualValues(t, 30, actualEvent.MultisigOperatorshipTransferred.NewThreshold.BigInt().Int64()) + }), + ). + Run(t, 5) +} diff --git a/vald/evm/token_confirmation.go b/vald/evm/token_confirmation.go new file mode 100644 index 000000000..7eb307a49 --- /dev/null +++ b/vald/evm/token_confirmation.go @@ -0,0 +1,77 @@ +package evm + +import ( + "bytes" + "context" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + + "github.com/axelarnetwork/axelar-core/x/evm/types" + voteTypes "github.com/axelarnetwork/axelar-core/x/vote/types" +) + +// ProcessTokenConfirmation votes on the correctness of an EVM chain token deployment +func (mgr Mgr) ProcessTokenConfirmation(event *types.ConfirmTokenStarted) error { + if !mgr.isParticipantOf(event.Participants) { + mgr.logger("pollID", event.PollID).Debug("ignoring token confirmation poll: not a participant") + return nil + } + + txReceipt, err := mgr.GetTxReceiptIfFinalized(event.Chain, common.Hash(event.TxID), event.ConfirmationHeight) + if err != nil { + return err + } + if txReceipt == nil { + mgr.logger().Infof("broadcasting empty vote for poll %s", event.PollID.String()) + _, err := mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain))) + + return err + } + + events := mgr.processTokenConfirmationLogs(event, txReceipt.Logs) + + mgr.logger().Infof("broadcasting vote %v for poll %s", events, event.PollID.String()) + _, err = mgr.broadcaster.Broadcast(context.TODO(), voteTypes.NewVoteRequest(mgr.proxy, event.PollID, types.NewVoteEvents(event.Chain, events...))) + + return err +} + +func (mgr Mgr) processTokenConfirmationLogs(event *types.ConfirmTokenStarted, logs []*geth.Log) []types.Event { + for i, log := range logs { + if log.Topics[0] != ERC20TokenDeploymentSig { + continue + } + + if !bytes.Equal(event.GatewayAddress.Bytes(), log.Address.Bytes()) { + continue + } + + erc20Event, err := DecodeERC20TokenDeploymentEvent(log) + if err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "decode event TokenDeployed failed").Error()) + continue + } + + if erc20Event.TokenAddress != event.TokenAddress || erc20Event.Symbol != event.TokenDetails.Symbol { + continue + } + + if err := erc20Event.ValidateBasic(); err != nil { + mgr.logger().Debug(sdkerrors.Wrap(err, "invalid event ERC20TokenDeployment").Error()) + continue + } + + return []types.Event{{ + Chain: event.Chain, + TxID: event.TxID, + Index: uint64(i), + Event: &types.Event_TokenDeployed{ + TokenDeployed: &erc20Event, + }, + }} + } + + return []types.Event{} +} diff --git a/vald/evm/token_confirmation_test.go b/vald/evm/token_confirmation_test.go new file mode 100644 index 000000000..97e934d32 --- /dev/null +++ b/vald/evm/token_confirmation_test.go @@ -0,0 +1,217 @@ +package evm_test + +import ( + "context" + "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + geth "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + + broadcastmock "github.com/axelarnetwork/axelar-core/sdk-utils/broadcast/mock" + "github.com/axelarnetwork/axelar-core/testutils" + "github.com/axelarnetwork/axelar-core/testutils/rand" + "github.com/axelarnetwork/axelar-core/vald/evm" + evmmock "github.com/axelarnetwork/axelar-core/vald/evm/mock" + evmrpc "github.com/axelarnetwork/axelar-core/vald/evm/rpc" + "github.com/axelarnetwork/axelar-core/vald/evm/rpc/mock" + "github.com/axelarnetwork/axelar-core/x/evm/types" + nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported" + vote "github.com/axelarnetwork/axelar-core/x/vote/exported" + votetypes "github.com/axelarnetwork/axelar-core/x/vote/types" +) + +func TestMgr_ProccessTokenConfirmation(t *testing.T) { + var ( + mgr *evm.Mgr + event *types.ConfirmTokenStarted + rpc *mock.ClientMock + broadcaster *broadcastmock.BroadcasterMock + gatewayAddrBytes []byte + valAddr sdk.ValAddress + ) + setup := func() { + pollID := vote.PollID(rand.I64Between(10, 100)) + + gatewayAddrBytes = rand.Bytes(common.AddressLength) + tokenAddrBytes := rand.Bytes(common.AddressLength) + blockNumber := rand.PInt64Gen().Where(func(i int64) bool { return i != 0 }).Next() // restrict to int64 so the block number in the receipt doesn't overflow + confHeight := rand.I64Between(0, blockNumber-1) + + symbol := rand.Denom(5, 20) + valAddr = rand.ValAddr() + event = &types.ConfirmTokenStarted{ + TxID: types.Hash(common.BytesToHash(rand.Bytes(common.HashLength))), + Chain: "Ethereum", + GatewayAddress: types.Address(common.BytesToAddress(gatewayAddrBytes)), + TokenAddress: types.Address(common.BytesToAddress(tokenAddrBytes)), + TokenDetails: types.TokenDetails{Symbol: symbol}, + ConfirmationHeight: uint64(confHeight), + PollParticipants: vote.PollParticipants{ + PollID: pollID, + Participants: []sdk.ValAddress{valAddr}, + }, + } + + tx := geth.NewTransaction(0, common.BytesToAddress(rand.Bytes(common.HashLength)), big.NewInt(0), 21000, big.NewInt(1), []byte{}) + receipt := &geth.Receipt{ + TxHash: tx.Hash(), + BlockNumber: big.NewInt(rand.I64Between(0, blockNumber-confHeight)), + Logs: createTokenLogs( + symbol, + common.BytesToAddress(gatewayAddrBytes), + common.BytesToAddress(tokenAddrBytes), + evm.ERC20TokenDeploymentSig, + true, + ), + Status: 1, + } + rpc = &mock.ClientMock{ + HeaderByNumberFunc: func(ctx context.Context, number *big.Int) (*evmrpc.Header, error) { + return &evmrpc.Header{Transactions: []common.Hash{receipt.TxHash}}, nil + }, + TransactionReceiptFunc: func(context.Context, common.Hash) (*geth.Receipt, error) { + return receipt, nil + }, + LatestFinalizedBlockNumberFunc: func(ctx context.Context, confirmations uint64) (*big.Int, error) { + return receipt.BlockNumber, nil + }, + } + broadcaster = &broadcastmock.BroadcasterMock{ + BroadcastFunc: func(context.Context, ...sdk.Msg) (*sdk.TxResponse, error) { return nil, nil }, + } + evmMap := make(map[string]evmrpc.Client) + evmMap["ethereum"] = rpc + mgr = evm.NewMgr(evmMap, broadcaster, valAddr, rand.AccAddr(), &evmmock.LatestFinalizedBlockCacheMock{ + GetFunc: func(_ nexus.ChainName) *big.Int { return big.NewInt(0) }, + SetFunc: func(_ nexus.ChainName, _ *big.Int) {}, + }) + } + + repeats := 20 + t.Run("happy path", testutils.Func(func(t *testing.T) { + setup() + + err := mgr.ProcessTokenConfirmation(event) + + assert.NoError(t, err) + assert.Len(t, broadcaster.BroadcastCalls(), 1) + + msg := broadcaster.BroadcastCalls()[0].Msgs[0] + actualVoteEvents := msg.(*votetypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) + assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) + assert.Len(t, actualVoteEvents.Events, 1) + }). + Repeat(repeats)) + + t.Run("no tx receipt", testutils.Func(func(t *testing.T) { + setup() + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return nil, ethereum.NotFound } + + err := mgr.ProcessTokenConfirmation(event) + + assert.NoError(t, err) + assert.Len(t, broadcaster.BroadcastCalls(), 1) + + msg := broadcaster.BroadcastCalls()[0].Msgs[0] + actualVoteEvents := msg.(*votetypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) + assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) + assert.Len(t, actualVoteEvents.Events, 0) + }). + Repeat(repeats)) + + t.Run("no deploy event", testutils.Func(func(t *testing.T) { + setup() + receipt, _ := rpc.TransactionReceipt(context.Background(), common.Hash{}) + var correctLogIdx int + for i, l := range receipt.Logs { + if l.Address == common.BytesToAddress(gatewayAddrBytes) { + correctLogIdx = i + break + } + } + // remove the deploy event + receipt.Logs = append(receipt.Logs[:correctLogIdx], receipt.Logs[correctLogIdx+1:]...) + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } + + err := mgr.ProcessTokenConfirmation(event) + + assert.NoError(t, err) + assert.Len(t, broadcaster.BroadcastCalls(), 1) + + msg := broadcaster.BroadcastCalls()[0].Msgs[0] + actualVoteEvents := msg.(*votetypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) + assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) + assert.Len(t, actualVoteEvents.Events, 0) + + }). + Repeat(repeats)) + + t.Run("wrong deploy event", testutils.Func(func(t *testing.T) { + setup() + receipt, _ := rpc.TransactionReceipt(context.Background(), common.Hash{}) + for _, l := range receipt.Logs { + if l.Address == common.BytesToAddress(gatewayAddrBytes) { + l.Data = rand.Bytes(int(rand.I64Between(0, 1000))) + break + } + } + rpc.TransactionReceiptFunc = func(context.Context, common.Hash) (*geth.Receipt, error) { return receipt, nil } + + err := mgr.ProcessTokenConfirmation(event) + + assert.NoError(t, err) + assert.Len(t, broadcaster.BroadcastCalls(), 1) + + msg := broadcaster.BroadcastCalls()[0].Msgs[0] + actualVoteEvents := msg.(*votetypes.VoteRequest).Vote.GetCachedValue().(*types.VoteEvents) + assert.Equal(t, nexus.ChainName("Ethereum"), actualVoteEvents.Chain) + assert.Len(t, actualVoteEvents.Events, 0) + }). + Repeat(repeats)) +} + +func createTokenLogs(denom string, gateway, tokenAddr common.Address, deploySig common.Hash, hasCorrectLog bool) []*geth.Log { + numLogs := rand.I64Between(1, 100) + correctPos := rand.I64Between(0, numLogs) + var logs []*geth.Log + + for i := int64(0); i < numLogs; i++ { + stringType, err := abi.NewType("string", "string", nil) + if err != nil { + panic(err) + } + addressType, err := abi.NewType("address", "address", nil) + if err != nil { + panic(err) + } + args := abi.Arguments{{Type: stringType}, {Type: addressType}} + + switch { + case hasCorrectLog && i == correctPos: + data, err := args.Pack(denom, tokenAddr) + if err != nil { + panic(err) + } + logs = append(logs, &geth.Log{Address: gateway, Data: data, Topics: []common.Hash{deploySig}}) + default: + randDenom := rand.StrBetween(5, 20) + randAddr := common.BytesToAddress(rand.Bytes(common.AddressLength)) + randData, err := args.Pack(randDenom, randAddr) + if err != nil { + panic(err) + } + logs = append(logs, &geth.Log{ + Address: common.BytesToAddress(rand.Bytes(common.AddressLength)), + Data: randData, + Topics: []common.Hash{common.BytesToHash(rand.Bytes(common.HashLength))}, + }) + } + } + + return logs +} From a30eae98fe1b71d43b42c03c1c5851917c18e1d0 Mon Sep 17 00:00:00 2001 From: jcs47 <11947034+jcs47@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:53:19 +0000 Subject: [PATCH 3/4] feat(cli): add `--wasm-dir` flag to start command (#2111) --- app/app.go | 8 +++++++- app/app_test.go | 1 + app/keepers.go | 4 +--- cmd/axelard/cmd/root.go | 16 +++++++++++++--- docs/cli/axelard_start.md | 1 + 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/app.go b/app/app.go index 9acdfd0b3..3605e9c22 100644 --- a/app/app.go +++ b/app/app.go @@ -202,6 +202,7 @@ func NewAxelarApp( loadLatest bool, skipUpgradeHeights map[int64]bool, homePath string, + wasmDir string, invCheckPeriod uint, encodingConfig axelarParams.EncodingConfig, appOpts servertypes.AppOptions, @@ -255,7 +256,12 @@ func NewAxelarApp( SetKeeper(keepers, initAxelarIBCKeeper(keepers)) if IsWasmEnabled() { - SetKeeper(keepers, initWasmKeeper(encodingConfig, keys, keepers, bApp, appOpts, wasmOpts, homePath)) + if wasmDir == "" { + dbDir := cast.ToString(appOpts.Get("db_dir")) + wasmDir = filepath.Join(homePath, dbDir, "wasm") + } + + SetKeeper(keepers, initWasmKeeper(encodingConfig, keys, keepers, bApp, appOpts, wasmOpts, wasmDir)) SetKeeper(keepers, initWasmContractKeeper(keepers)) // set the contract keeper for the Ics20WasmHooks diff --git a/app/app_test.go b/app/app_test.go index 558f4689a..a8698b0e1 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -41,6 +41,7 @@ func TestNewAxelarApp(t *testing.T) { true, nil, "", + "", 0, app.MakeEncodingConfig(), simapp.EmptyAppOptions{}, diff --git a/app/keepers.go b/app/keepers.go index 94cf736de..e8ae81af4 100644 --- a/app/keepers.go +++ b/app/keepers.go @@ -2,7 +2,6 @@ package app import ( "fmt" - "path/filepath" "reflect" "strings" @@ -162,8 +161,7 @@ func InitStakingKeeper(appCodec codec.Codec, keys map[string]*sdk.KVStoreKey, ke return &stakingK } -func initWasmKeeper(encodingConfig axelarParams.EncodingConfig, keys map[string]*sdk.KVStoreKey, keepers *KeeperCache, bApp *bam.BaseApp, appOpts types.AppOptions, wasmOpts []wasm.Option, homePath string) *wasm.Keeper { - wasmDir := filepath.Join(homePath, "wasm") +func initWasmKeeper(encodingConfig axelarParams.EncodingConfig, keys map[string]*sdk.KVStoreKey, keepers *KeeperCache, bApp *bam.BaseApp, appOpts types.AppOptions, wasmOpts []wasm.Option, wasmDir string) *wasm.Keeper { wasmConfig := mustReadWasmConfig(appOpts) // The last arguments can contain custom message handlers, and custom query handlers, diff --git a/cmd/axelard/cmd/root.go b/cmd/axelard/cmd/root.go index 5f9f3d685..045f0b718 100644 --- a/cmd/axelard/cmd/root.go +++ b/cmd/axelard/cmd/root.go @@ -47,7 +47,10 @@ import ( axelarnet "github.com/axelarnetwork/axelar-core/x/axelarnet/exported" ) -var minGasPrice = "0.007" + axelarnet.NativeAsset +var ( + minGasPrice = "0.007" + axelarnet.NativeAsset + wasmDirFlag = "wasm-dir" +) // NewRootCmd creates a new root command for axelard. It is called once in the // main function. @@ -201,7 +204,12 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { SetGenesisAuthCmd(app.DefaultNodeHome), ) - server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, export(encodingConfig), crisis.AddModuleInitFlags) + starterFlags := func(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) + startCmd.Flags().String(wasmDirFlag, "", "path to the wasm directory, by default set to 'wasm' directory inside the '--db_dir' directory") + } + + server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, export(encodingConfig), starterFlags) // add keybase, auxiliary RPC, query, and tx child commands rootCmd.AddCommand( @@ -264,6 +272,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts serverty return app.NewAxelarApp( logger, db, traceStore, true, skipUpgradeHeights, cast.ToString(appOpts.Get(flags.FlagHome)), + cast.ToString(appOpts.Get(wasmDirFlag)), cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)), app.MakeEncodingConfig(), appOpts, @@ -291,7 +300,8 @@ func export(encCfg params.EncodingConfig) servertypes.AppExporter { return servertypes.ExportedApp{}, errors.New("application home not set") } - aApp := app.NewAxelarApp(logger, db, traceStore, height == -1, map[int64]bool{}, homePath, + wasmdir := cast.ToString(appOpts.Get(wasmDirFlag)) + aApp := app.NewAxelarApp(logger, db, traceStore, height == -1, map[int64]bool{}, homePath, wasmdir, cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)), encCfg, appOpts, []wasm.Option{}) if height != -1 { if err := aApp.LoadHeight(height); err != nil { diff --git a/docs/cli/axelard_start.md b/docs/cli/axelard_start.md index 5ba47b2c5..b82c4d9a7 100644 --- a/docs/cli/axelard_start.md +++ b/docs/cli/axelard_start.md @@ -88,6 +88,7 @@ axelard start [flags] --trace-store string Enable KVStore tracing to an output file --transport string Transport protocol: socket, grpc (default "socket") --unsafe-skip-upgrades ints Skip a set of upgrade heights to continue the old binary + --wasm-dir string path to the wasm directory, by default set to 'wasm' directory inside the '--db_dir' directory --with-tendermint Run abci app embedded in-process with tendermint (default true) --x-crisis-skip-assert-invariants Skip x/crisis invariants check on startup ``` From 6f90d60c5a2165a486b74a02c6f4a53b821fcb79 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Feb 2024 16:09:07 -0500 Subject: [PATCH 4/4] fix(app): register wasm snapshot extension for state-sync (#2114) * fix(app): register wasm snapshot extension for state-sync * feature gate extension registration * move to a separate function * rename method --- app/app.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/app.go b/app/app.go index 3605e9c22..085a0e1fa 100644 --- a/app/app.go +++ b/app/app.go @@ -344,6 +344,10 @@ func NewAxelarApp( app.SetAnteHandler(initAnteHandlers(encodingConfig, keys, keepers, appOpts)) + // Register wasm snapshot extension for state-sync compatibility + // MUST be done before loading the version + app.registerWasmSnapshotExtension(keepers) + if loadLatest { if err := app.LoadLatestVersion(); err != nil { tmos.Exit(err.Error()) @@ -453,6 +457,22 @@ func initMessageRouter(keepers *KeeperCache) nexusTypes.MessageRouter { return messageRouter } +func (app *AxelarApp) registerWasmSnapshotExtension(keepers *KeeperCache) { + // Register wasm snapshot extension to enable state-sync compatibility for wasm. + // MUST be done before loading the version + // Requires the snapshot store to be created and registered as a BaseAppOption + if IsWasmEnabled() { + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + wasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), getKeeper[wasm.Keeper](keepers)), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + } +} + func (app *AxelarApp) setUpgradeBehaviour(configurator module.Configurator, keepers *KeeperCache) { app.upgradeKeeper.SetUpgradeHandler( upgradeName(app.Version()),