diff --git a/changelog.md b/changelog.md index 6bd82b0ae6..4914e7a500 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,8 @@ ### Features +* [2578](https://github.com/zeta-chain/node/pull/2578) - add Gateway address in protocol contract list +* [2630](https://github.com/zeta-chain/node/pull/2630) - implement `MsgMigrateERC20CustodyFunds` to migrate the funds from the ERC20Custody to a new contracts (to be used for the new ERC20Custody contract for smart contract V2) * [2578](https://github.com/zeta-chain/node/pull/2578) - Add Gateway address in protocol contract list * [2634](https://github.com/zeta-chain/node/pull/2634) - add support for EIP-1559 gas fees * [2597](https://github.com/zeta-chain/node/pull/2597) - Add generic rpc metrics to zetaclient @@ -133,6 +135,7 @@ * [2481](https://github.com/zeta-chain/node/pull/2481) - increase gas limit inbound and outbound vote message to 500k * [2545](https://github.com/zeta-chain/node/pull/2545) - check solana minimum rent exempt to avoid outbound failure * [2547](https://github.com/zeta-chain/node/pull/2547) - limit max txs in priority mempool +* [2628](https://github.com/zeta-chain/node/pull/2628) - avoid submitting invalid hashes to outbound tracker ### CI diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 86adfac7c4..521c53569e 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -302,6 +302,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestUpdateBytecodeConnectorName, e2etests.TestDepositEtherLiquidityCapName, e2etests.TestCriticalAdminTransactionsName, + e2etests.TestMigrateERC20CustodyFundsName, // TestMigrateChainSupportName tests EVM chain migration. Currently this test doesn't work with Anvil because pre-EIP1559 txs are not supported // See issue below for details diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index 243e13c3af..137c3d1205 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -57231,6 +57231,11 @@ definitions: is_removed: type: boolean title: if the tx was removed from the tracker due to no pending cctx + crosschainMsgMigrateERC20CustodyFundsResponse: + type: object + properties: + cctx_index: + type: string crosschainMsgMigrateTssFundsResponse: type: object crosschainMsgRefundAbortedCCTXResponse: diff --git a/docs/spec/crosschain/messages.md b/docs/spec/crosschain/messages.md index feb02afc1f..0f032b2d34 100644 --- a/docs/spec/crosschain/messages.md +++ b/docs/spec/crosschain/messages.md @@ -272,3 +272,17 @@ message MsgUpdateRateLimiterFlags { } ``` +## MsgMigrateERC20CustodyFunds + +MigrateERC20CustodyFunds migrates the funds from the current ERC20Custody contract to the new ERC20Custody contract + +```proto +message MsgMigrateERC20CustodyFunds { + string creator = 1; + int64 chain_id = 2; + string new_custody_address = 3; + string erc20_address = 4; + string amount = 5; +} +``` + diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 402eed367e..1115d7d6c2 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -108,6 +108,7 @@ const ( TestUpdateBytecodeConnectorName = "update_bytecode_connector" TestRateLimiterName = "rate_limiter" TestCriticalAdminTransactionsName = "critical_admin_transactions" + TestMigrateERC20CustodyFundsName = "migrate_erc20_custody_funds" TestMigrateTSSName = "migrate_TSS" @@ -572,6 +573,12 @@ var AllE2ETests = []runner.E2ETest{ []runner.ArgDefinition{}, TestCriticalAdminTransactions, ), + runner.NewE2ETest( + TestMigrateERC20CustodyFundsName, + "migrate ERC20 custody funds", + []runner.ArgDefinition{}, + TestMigrateERC20CustodyFunds, + ), /* Special tests */ diff --git a/e2e/e2etests/test_migrate_erc20_custody_funds.go b/e2e/e2etests/test_migrate_erc20_custody_funds.go new file mode 100644 index 0000000000..1c38909b3d --- /dev/null +++ b/e2e/e2etests/test_migrate_erc20_custody_funds.go @@ -0,0 +1,61 @@ +package e2etests + +import ( + sdkmath "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/txserver" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/testutil/sample" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// TestMigrateERC20CustodyFunds tests the migration of ERC20 custody funds +func TestMigrateERC20CustodyFunds(r *runner.E2ERunner, _ []string) { + // get erc20 balance on ERC20 custody contract + balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyAddr) + require.NoError(r, err) + + // get EVM chain ID + chainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + + newAddr := sample.EthAddress() + + // send MigrateERC20CustodyFunds command + // NOTE: we currently use a random address for the destination as a sufficient way to check migration + // TODO: makes the test more complete and perform a withdraw to new custody once the contract V2 architecture is integrated + // https://github.com/zeta-chain/node/issues/2474 + msg := crosschaintypes.NewMsgMigrateERC20CustodyFunds( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + chainID.Int64(), + newAddr.Hex(), + r.ERC20Addr.Hex(), + sdkmath.NewUintFromBigInt(balance), + ) + res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msg) + require.NoError(r, err) + + // fetch cctx index from tx response + cctxIndex, err := txserver.FetchAttributeFromTxResponse(res, "cctx_index") + require.NoError(r, err) + + cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) + require.NoError(r, err) + + cctx := cctxRes.CrossChainTx + r.Logger.CCTX(*cctx, "migration") + + // wait for the cctx to be mined + r.WaitForMinedCCTXFromIndex(cctxIndex) + + // check ERC20 balance on new address + newAddrBalance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, newAddr) + require.NoError(r, err) + require.Equal(r, balance, newAddrBalance) + + // artificially set the ERC20 Custody address to the new address to prevent accounting check from failing + r.ERC20CustodyAddr = newAddr +} diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index a10dc8d68b..9122860d75 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -20,7 +20,7 @@ const ( AdminPolicyName = "admin" OperationalPolicyName = "operational" - DefaultCctxTimeout = 4 * time.Minute + DefaultCctxTimeout = 6 * time.Minute ) // WaitCctxMinedByInboundHash waits until cctx is mined; returns the cctxIndex (the last one) diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index 6aba0f4dea..57a922e5c0 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -1,6 +1,12 @@ package constant +import "time" + const ( + // ZetaBlockTime is the block time of the ZetaChain network + // It's a rough estimate that can be used in non-critical path to estimate the time of a block + ZetaBlockTime = 6000 * time.Millisecond + // DonationMessage is the message for donation transactions // Transaction sent to the TSS or ERC20 Custody address containing this message are considered as a donation DonationMessage = "I am rich!" @@ -8,6 +14,9 @@ const ( // CmdWhitelistERC20 is used for CCTX of type cmd to give the instruction to the TSS to whitelist an ERC20 on an exeternal chain CmdWhitelistERC20 = "cmd_whitelist_erc20" + // CmdMigrateERC20CustodyFunds is used for CCTX of type cmd to give the instruction to the TSS to transfer its funds on a new address + CmdMigrateERC20CustodyFunds = "cmd_migrate_erc20_custody_funds" + // CmdMigrateTssFunds is used for CCTX of type cmd to give the instruction to the TSS to transfer its funds on a new address CmdMigrateTssFunds = "cmd_migrate_tss_funds" diff --git a/proto/zetachain/zetacore/crosschain/events.proto b/proto/zetachain/zetacore/crosschain/events.proto index 3778b3bdce..5ec523d9e4 100644 --- a/proto/zetachain/zetacore/crosschain/events.proto +++ b/proto/zetachain/zetacore/crosschain/events.proto @@ -68,3 +68,10 @@ message EventERC20Whitelist { string whitelist_cctx_index = 1; string zrc20_address = 2; } + +message EventERC20CustodyFundsMigration { + string new_custody_address = 1; + string erc20_address = 2; + string amount = 3; + string cctx_index = 4; +} diff --git a/proto/zetachain/zetacore/crosschain/tx.proto b/proto/zetachain/zetacore/crosschain/tx.proto index 2bff84f733..0cd0c8e1f5 100644 --- a/proto/zetachain/zetacore/crosschain/tx.proto +++ b/proto/zetachain/zetacore/crosschain/tx.proto @@ -33,6 +33,9 @@ service Msg { rpc UpdateRateLimiterFlags(MsgUpdateRateLimiterFlags) returns (MsgUpdateRateLimiterFlagsResponse); + + rpc MigrateERC20CustodyFunds(MsgMigrateERC20CustodyFunds) + returns (MsgMigrateERC20CustodyFundsResponse); } message MsgMigrateTssFunds { @@ -188,3 +191,16 @@ message MsgUpdateRateLimiterFlags { } message MsgUpdateRateLimiterFlagsResponse {} + +message MsgMigrateERC20CustodyFunds { + string creator = 1; + int64 chain_id = 2; + string new_custody_address = 3; + string erc20_address = 4; + string amount = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +message MsgMigrateERC20CustodyFundsResponse { string cctx_index = 1; } diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index 4fd1e70018..2894431724 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -131,6 +131,19 @@ func GasPrice(t *testing.T, index string) *types.GasPrice { } } +func GasPriceWithChainID(t *testing.T, chainID int64) types.GasPrice { + r := newRandFromStringSeed(t, fmt.Sprintf("%d", chainID)) + + return types.GasPrice{ + Creator: AccAddress(), + ChainId: chainID, + Signers: []string{AccAddress(), AccAddress()}, + BlockNums: []uint64{r.Uint64(), r.Uint64()}, + Prices: []uint64{r.Uint64(), r.Uint64()}, + MedianIndex: 0, + } +} + func InboundParams(r *rand.Rand) *types.InboundParams { return &types.InboundParams{ Sender: EthAddress().String(), diff --git a/typescript/zetachain/zetacore/crosschain/events_pb.d.ts b/typescript/zetachain/zetacore/crosschain/events_pb.d.ts index 2a71867f7b..c46555a9e3 100644 --- a/typescript/zetachain/zetacore/crosschain/events_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/events_pb.d.ts @@ -354,3 +354,42 @@ export declare class EventERC20Whitelist extends Message { static equals(a: EventERC20Whitelist | PlainMessage | undefined, b: EventERC20Whitelist | PlainMessage | undefined): boolean; } +/** + * @generated from message zetachain.zetacore.crosschain.EventERC20CustodyFundsMigration + */ +export declare class EventERC20CustodyFundsMigration extends Message { + /** + * @generated from field: string new_custody_address = 1; + */ + newCustodyAddress: string; + + /** + * @generated from field: string erc20_address = 2; + */ + erc20Address: string; + + /** + * @generated from field: string amount = 3; + */ + amount: string; + + /** + * @generated from field: string cctx_index = 4; + */ + cctxIndex: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.EventERC20CustodyFundsMigration"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): EventERC20CustodyFundsMigration; + + static fromJson(jsonValue: JsonValue, options?: Partial): EventERC20CustodyFundsMigration; + + static fromJsonString(jsonString: string, options?: Partial): EventERC20CustodyFundsMigration; + + static equals(a: EventERC20CustodyFundsMigration | PlainMessage | undefined, b: EventERC20CustodyFundsMigration | PlainMessage | undefined): boolean; +} + diff --git a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts index e15cf71780..52d4b8aaa9 100644 --- a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts @@ -826,3 +826,71 @@ export declare class MsgUpdateRateLimiterFlagsResponse extends Message | undefined, b: MsgUpdateRateLimiterFlagsResponse | PlainMessage | undefined): boolean; } +/** + * @generated from message zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds + */ +export declare class MsgMigrateERC20CustodyFunds extends Message { + /** + * @generated from field: string creator = 1; + */ + creator: string; + + /** + * @generated from field: int64 chain_id = 2; + */ + chainId: bigint; + + /** + * @generated from field: string new_custody_address = 3; + */ + newCustodyAddress: string; + + /** + * @generated from field: string erc20_address = 4; + */ + erc20Address: string; + + /** + * @generated from field: string amount = 5; + */ + amount: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgMigrateERC20CustodyFunds; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgMigrateERC20CustodyFunds; + + static fromJsonString(jsonString: string, options?: Partial): MsgMigrateERC20CustodyFunds; + + static equals(a: MsgMigrateERC20CustodyFunds | PlainMessage | undefined, b: MsgMigrateERC20CustodyFunds | PlainMessage | undefined): boolean; +} + +/** + * @generated from message zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFundsResponse + */ +export declare class MsgMigrateERC20CustodyFundsResponse extends Message { + /** + * @generated from field: string cctx_index = 1; + */ + cctxIndex: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFundsResponse"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgMigrateERC20CustodyFundsResponse; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgMigrateERC20CustodyFundsResponse; + + static fromJsonString(jsonString: string, options?: Partial): MsgMigrateERC20CustodyFundsResponse; + + static equals(a: MsgMigrateERC20CustodyFundsResponse | PlainMessage | undefined, b: MsgMigrateERC20CustodyFundsResponse | PlainMessage | undefined): boolean; +} + diff --git a/x/authority/types/authorization_list.go b/x/authority/types/authorization_list.go index 58f3d80063..6d55a6770e 100644 --- a/x/authority/types/authorization_list.go +++ b/x/authority/types/authorization_list.go @@ -23,6 +23,7 @@ var ( } // AdminPolicyMessages keeps track of the message URLs that can, by default, only be executed by admin policy address AdminPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds", "/zetachain.zetacore.crosschain.MsgMigrateTssFunds", "/zetachain.zetacore.crosschain.MsgUpdateTssAddress", "/zetachain.zetacore.crosschain.MsgWhitelistERC20", diff --git a/x/authority/types/authorization_list_test.go b/x/authority/types/authorization_list_test.go index 2322caa798..2f45b3b2c4 100644 --- a/x/authority/types/authorization_list_test.go +++ b/x/authority/types/authorization_list_test.go @@ -414,6 +414,7 @@ func TestDefaultAuthorizationsList(t *testing.T) { // AdminPolicyMessageList is a list of messages that can be authorized by the admin policy var AdminPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgMigrateERC20CustodyFunds{}), sdk.MsgTypeURL(&crosschaintypes.MsgMigrateTssFunds{}), sdk.MsgTypeURL(&crosschaintypes.MsgUpdateTssAddress{}), sdk.MsgTypeURL(&crosschaintypes.MsgWhitelistERC20{}), diff --git a/x/crosschain/keeper/cctx_utils.go b/x/crosschain/keeper/cctx_utils.go index 0d6f760fda..6be78bdfaa 100644 --- a/x/crosschain/keeper/cctx_utils.go +++ b/x/crosschain/keeper/cctx_utils.go @@ -12,7 +12,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" - zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // SetObserverOutboundInfo sets the CCTX outbound nonce to the next available nonce for the TSS address, and updates the nonce of blockchain state. @@ -20,7 +20,7 @@ import ( func (k Keeper) SetObserverOutboundInfo(ctx sdk.Context, receiveChainID int64, cctx *types.CrossChainTx) error { chain, found := k.GetObserverKeeper().GetSupportedChainFromChainID(ctx, receiveChainID) if !found { - return zetaObserverTypes.ErrSupportedChains + return observertypes.ErrSupportedChains } nonce, found := k.GetObserverKeeper().GetChainNonces(ctx, receiveChainID) diff --git a/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds.go b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds.go new file mode 100644 index 0000000000..56bc0a5ea1 --- /dev/null +++ b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds.go @@ -0,0 +1,103 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// MigrateERC20CustodyFunds migrates the funds from the current ERC20Custody contract to the new ERC20Custody contract +func (k msgServer) MigrateERC20CustodyFunds( + goCtx context.Context, + msg *types.MsgMigrateERC20CustodyFunds, +) (*types.MsgMigrateERC20CustodyFundsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // check if authorized + err := k.GetAuthorityKeeper().CheckAuthorization(ctx, msg) + if err != nil { + return nil, errorsmod.Wrap(authoritytypes.ErrUnauthorized, err.Error()) + } + + // get the current TSS nonce allow to set a unique index for the CCTX + chainNonce, found := k.GetObserverKeeper().GetChainNonces(ctx, msg.ChainId) + if !found { + return nil, errorsmod.Wrap(types.ErrInvalidChainID, "cannot find current chain nonce") + } + currentNonce := chainNonce.Nonce + + // get the current TSS + tss, found := k.GetObserverKeeper().GetTSS(ctx) + if !found { + return nil, errorsmod.Wrap(types.ErrCannotFindTSSKeys, "cannot find current TSS") + } + + // get necessary parameters to create the cctx + params, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) + if !found { + return nil, errorsmod.Wrapf(types.ErrInvalidChainID, "chain params not found for chain id (%d)", msg.ChainId) + } + medianGasPrice, priorityFee, isFound := k.GetMedianGasValues(ctx, msg.ChainId) + if !isFound { + return nil, errorsmod.Wrapf( + types.ErrUnableToGetGasPrice, + "median gas price not found for chain id (%d)", + msg.ChainId, + ) + } + + // overpays gas price by 2x + medianGasPrice = medianGasPrice.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM) + priorityFee = priorityFee.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM) + + // should not happen + if priorityFee.GT(medianGasPrice) { + return nil, errorsmod.Wrapf( + types.ErrInvalidGasAmount, + "priorityFee %s is greater than median gasPrice %s", + priorityFee.String(), + medianGasPrice.String(), + ) + } + + // create the CCTX that allows to sign the fund migration + cctx := types.MigrateERC20CustodyFundsCmdCCTX( + msg.Creator, + msg.Erc20Address, + params.Erc20CustodyContractAddress, + msg.NewCustodyAddress, + msg.ChainId, + msg.Amount, + medianGasPrice.String(), + priorityFee.String(), + tss.TssPubkey, + currentNonce, + ) + + // save the cctx + err = k.SetObserverOutboundInfo(ctx, msg.ChainId, &cctx) + if err != nil { + return nil, err + } + k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) + + err = ctx.EventManager().EmitTypedEvent( + &types.EventERC20CustodyFundsMigration{ + NewCustodyAddress: msg.NewCustodyAddress, + Erc20Address: msg.Erc20Address, + Amount: msg.Amount.String(), + CctxIndex: cctx.Index, + }, + ) + if err != nil { + return nil, errorsmod.Wrapf(err, "failed to emit event") + } + + return &types.MsgMigrateERC20CustodyFundsResponse{ + CctxIndex: cctx.Index, + }, nil +} diff --git a/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds_test.go b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds_test.go new file mode 100644 index 0000000000..c68df89467 --- /dev/null +++ b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds_test.go @@ -0,0 +1,336 @@ +package keeper_test + +import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/coin" + testkeeper "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + "github.com/zeta-chain/zetacore/x/crosschain/keeper" + "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "testing" +) + +func TestKeeper_MigrateERC20CustodyFunds(t *testing.T) { + t.Run("can create CCTX to migrate ERC20 custody funds", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + medianGasPrice, priorityFee, isFound := k.GetMedianGasValues(ctx, msg.ChainId) + require.True(t, isFound) + + // ACT + res, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.NoError(t, err) + + // check CCTX is created + cctx, found := k.GetCrossChainTx(ctx, res.CctxIndex) + require.True(t, found) + require.Equal(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues( + t, + medianGasPrice.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM).String(), + cctx.OutboundParams[0].GasPrice, + ) + require.EqualValues( + t, + priorityFee.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM).String(), + cctx.OutboundParams[0].GasPriorityFee, + ) + }) + + t.Run("should fail if not authorized", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + msgServer := keeper.NewMsgServerImpl(*k) + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: getValidEthChain().ChainId, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, errors.New("not authorized")) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, authoritytypes.ErrUnauthorized) + }) + + t.Run("should fail if can't find chain nonces", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + //zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) // not set + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidChainID) + }) + + t.Run("should fail if can't find current TSS", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + //zk.ObserverKeeper.SetTSS(ctx, tss) // not set + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrCannotFindTSSKeys) + }) + + t.Run("should fail if can't find chain params", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{}) // not set + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidChainID) + }) + + t.Run("should fail if can't find gas price", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + //k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) // not set + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrUnableToGetGasPrice) + }) + + t.Run("should fail if priority fees higher than gas price", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, types.GasPrice{ + Creator: sample.AccAddress(), + ChainId: chainID, + Signers: []string{sample.AccAddress()}, + BlockNums: []uint64{42}, + Prices: []uint64{42}, + PriorityFees: []uint64{43}, + MedianIndex: 0, + }) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidGasAmount) + }) + + t.Run("should fail if can't set outbound info", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{ + sample.ChainParams(chainID), + }, // set non supported chain params to fail + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, observertypes.ErrSupportedChains) + }) +} diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds.go b/x/crosschain/keeper/msg_server_migrate_tss_funds.go index ed8c51b93d..7d48b48cdb 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" "sort" errorsmod "cosmossdk.io/errors" @@ -11,13 +10,7 @@ import ( tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/crypto" - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/pkg/constant" - zetacrypto "github.com/zeta-chain/zetacore/pkg/crypto" - "github.com/zeta-chain/zetacore/pkg/gas" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -72,7 +65,7 @@ func (k msgServer) MigrateTssFunds( return nil, errorsmod.Wrap(types.ErrCannotMigrateTssFunds, "cannot migrate funds when there are pending nonces") } - err = k.MigrateTSSFundsForChain(ctx, msg.ChainId, msg.Amount, tss, tssHistory) + err = k.initiateMigrateTSSFundsCCTX(ctx, msg.Creator, msg.ChainId, msg.Amount, tss, tssHistory) if err != nil { return nil, errorsmod.Wrap(types.ErrCannotMigrateTssFunds, err.Error()) } @@ -80,8 +73,10 @@ func (k msgServer) MigrateTssFunds( return &types.MsgMigrateTssFundsResponse{}, nil } -func (k Keeper) MigrateTSSFundsForChain( +// initiateMigrateTSSFundsCCTX sets the CCTX for migrating the funds to initiate the migration outbound +func (k Keeper) initiateMigrateTSSFundsCCTX( ctx sdk.Context, + creator string, chainID int64, amount sdkmath.Uint, currentTss observertypes.TSS, @@ -93,126 +88,24 @@ func (k Keeper) MigrateTSSFundsForChain( if !isFound { return types.ErrUnableToGetGasPrice } - indexString := GetIndexStringForTssMigration( - currentTss.TssPubkey, - newTss.TssPubkey, + + // initialize the cmd CCTX + cctx, err := types.MigrateFundCmdCCTX( + ctx.BlockHeight(), + creator, + tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()).String(), chainID, amount, - ctx.BlockHeight(), + medianGasPrice, + priorityFee, + currentTss.TssPubkey, + newTss.TssPubkey, + k.GetAuthorityKeeper().GetAdditionalChainList(ctx), ) - - hash := crypto.Keccak256Hash([]byte(indexString)) - index := hash.Hex() - - // TODO : Use the `NewCCTX` method to create the cctx - // https://github.com/zeta-chain/node/issues/1909 - cctx := types.CrossChainTx{ - Creator: "", - Index: index, - ZetaFees: sdkmath.Uint{}, - RelayedMessage: fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), - CctxStatus: &types.Status{ - Status: types.CctxStatus_PendingOutbound, - StatusMessage: "", - LastUpdateTimestamp: 0, - }, - InboundParams: &types.InboundParams{ - Sender: "", - SenderChainId: chainID, - TxOrigin: "", - CoinType: coin.CoinType_Cmd, - Asset: "", - Amount: amount, - ObservedHash: tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()).String(), - ObservedExternalHeight: 0, - BallotIndex: "", - FinalizedZetaHeight: 0, - }, - OutboundParams: []*types.OutboundParams{{ - Receiver: "", - ReceiverChainId: chainID, - CoinType: coin.CoinType_Cmd, - Amount: amount, - TssNonce: 0, - GasLimit: 1_000_000, - GasPrice: medianGasPrice.MulUint64(2).String(), - GasPriorityFee: priorityFee.MulUint64(2).String(), - Hash: "", - BallotIndex: "", - ObservedExternalHeight: 0, - GasUsed: 0, - EffectiveGasPrice: sdkmath.Int{}, - EffectiveGasLimit: 0, - TssPubkey: currentTss.TssPubkey, - }}, - } - - // retrieve from authority keeper additional chains - additionalChains := k.GetAuthorityKeeper().GetAdditionalChainList(ctx) - - // Set the sender and receiver addresses for EVM chain - if chains.IsEVMChain(chainID, additionalChains) { - ethAddressOld, err := zetacrypto.GetTssAddrEVM(currentTss.TssPubkey) - if err != nil { - return err - } - ethAddressNew, err := zetacrypto.GetTssAddrEVM(newTss.TssPubkey) - if err != nil { - return err - } - cctx.InboundParams.Sender = ethAddressOld.String() - cctx.GetCurrentOutboundParam().Receiver = ethAddressNew.String() - // Tss migration is a send transaction, so the gas limit is set to 21000 - cctx.GetCurrentOutboundParam().GasLimit = gas.EVMSend - // Multiple current gas price with standard multiplier to add some buffer - multipliedGasPrice, err := gas.MultiplyGasPrice(medianGasPrice, types.TssMigrationGasMultiplierEVM) - if err != nil { - return err - } - evmFee := sdkmath.NewUint(cctx.GetCurrentOutboundParam().GasLimit).Mul(multipliedGasPrice) - if evmFee.GT(amount) { - return errorsmod.Wrap( - types.ErrInsufficientFundsTssMigration, - fmt.Sprintf( - "insufficient funds to pay for gas fee, amount: %s, gas fee: %s, chainid: %d", - amount.String(), - evmFee.String(), - chainID, - ), - ) - } - - cctx.GetCurrentOutboundParam().GasPrice = multipliedGasPrice.String() - cctx.GetCurrentOutboundParam().Amount = amount.Sub( - evmFee.Add(sdkmath.NewUintFromString(types.TSSMigrationBufferAmountEVM)), - ) - } - // Set the sender and receiver addresses for Bitcoin chain - if chains.IsBitcoinChain(chainID, additionalChains) { - bitcoinNetParams, err := chains.BitcoinNetParamsFromChainID(chainID) - if err != nil { - return err - } - btcAddressOld, err := zetacrypto.GetTssAddrBTC(currentTss.TssPubkey, bitcoinNetParams) - if err != nil { - return err - } - btcAddressNew, err := zetacrypto.GetTssAddrBTC(newTss.TssPubkey, bitcoinNetParams) - if err != nil { - return err - } - cctx.InboundParams.Sender = btcAddressOld - cctx.GetCurrentOutboundParam().Receiver = btcAddressNew - } - - if cctx.GetCurrentOutboundParam().Receiver == "" { - return errorsmod.Wrap(types.ErrReceiverIsEmpty, fmt.Sprintf("chain %d is not supported", chainID)) - } - - err := k.SetObserverOutboundInfo(ctx, chainID, &cctx) if err != nil { return err } + // The migrate funds can be run again to update the migration cctx index if the migration fails // This should be used after carefully calculating the amount again existingMigrationInfo, found := k.zetaObserverKeeper.GetFundMigrator(ctx, chainID) @@ -238,18 +131,9 @@ func (k Keeper) MigrateTSSFundsForChain( k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) k.zetaObserverKeeper.SetFundMigrator(ctx, observertypes.TssFundMigratorInfo{ ChainId: chainID, - MigrationCctxIndex: index, + MigrationCctxIndex: cctx.Index, }) EmitEventInboundFinalized(ctx, &cctx) return nil } - -func GetIndexStringForTssMigration( - currentTssPubkey, newTssPubkey string, - chainID int64, - amount sdkmath.Uint, - height int64, -) string { - return fmt.Sprintf("%s-%s-%d-%s-%d", currentTssPubkey, newTssPubkey, chainID, amount.String(), height) -} diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go index 1896a35f59..a715fcf4e6 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go @@ -74,7 +74,7 @@ func setupTssMigrationParams( ChainId: chain.ChainId, Nonce: 1, }) - indexString := keeper.GetIndexStringForTssMigration( + indexString := crosschaintypes.GetTssMigrationCCTXIndexString( currentTss.TssPubkey, newTss.TssPubkey, chain.ChainId, @@ -110,10 +110,12 @@ func TestKeeper_MigrateTSSFundsForChain(t *testing.T) { keepertest.MockGetChainListEmpty(&authorityMock.Mock) _, err := msgServer.MigrateTssFunds(ctx, &msg) require.NoError(t, err) + hash := crypto.Keccak256Hash([]byte(indexString)) index := hash.Hex() cctx, found := k.GetCrossChainTx(ctx, index) require.True(t, found) + multipliedValue, err := gas.MultiplyGasPrice(gp, crosschaintypes.TssMigrationGasMultiplierEVM) require.NoError(t, err) require.Equal(t, multipliedValue.String(), cctx.GetCurrentOutboundParam().GasPrice) diff --git a/x/crosschain/keeper/msg_server_whitelist_erc20.go b/x/crosschain/keeper/msg_server_whitelist_erc20.go index 8b6ad08b26..45cf836061 100644 --- a/x/crosschain/keeper/msg_server_whitelist_erc20.go +++ b/x/crosschain/keeper/msg_server_whitelist_erc20.go @@ -2,18 +2,14 @@ package keeper import ( "context" - "fmt" "math/big" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/pkg/constant" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" @@ -101,7 +97,7 @@ func (k msgServer) WhitelistERC20( } // get necessary parameters to create the cctx - param, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) + params, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) if !found { return nil, errorsmod.Wrapf(types.ErrInvalidChainID, "chain params not found for chain id (%d)", msg.ChainId) } @@ -113,11 +109,10 @@ func (k msgServer) WhitelistERC20( msg.ChainId, ) } - // overpays gas price by 2x - const multiplier = 2 - medianGasPrice = medianGasPrice.MulUint64(multiplier) - priorityFee = priorityFee.MulUint64(multiplier) + // overpays gas price by 2x + medianGasPrice = medianGasPrice.MulUint64(types.ERC20CustodyWhitelistGasMultiplierEVM) + priorityFee = priorityFee.MulUint64(types.ERC20CustodyWhitelistGasMultiplierEVM) // should not happen if priorityFee.GT(medianGasPrice) { @@ -129,54 +124,18 @@ func (k msgServer) WhitelistERC20( ) } - // calculate the cctx index - // we use the deployed zrc20 contract address to generate a unique index - // since other parts of the system may use the zrc20 for the index, we add a message specific suffix - hash := crypto.Keccak256Hash(zrc20Addr.Bytes(), []byte("WhitelistERC20")) - index := hash.Hex() - - // create a cmd cctx to whitelist the erc20 on the external chain - // TODO : refactor this to use the `NewCCTX` function instead. - //https://github.com/zeta-chain/node/issues/1909 - cctx := types.CrossChainTx{ - Creator: msg.Creator, - Index: index, - ZetaFees: sdk.NewUint(0), - RelayedMessage: fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, msg.Erc20Address), - CctxStatus: &types.Status{ - Status: types.CctxStatus_PendingOutbound, - StatusMessage: "", - LastUpdateTimestamp: 0, - }, - InboundParams: &types.InboundParams{ - Sender: msg.Creator, - SenderChainId: 0, - TxOrigin: "", - CoinType: coin.CoinType_Cmd, - Asset: "", - Amount: math.Uint{}, - ObservedHash: hash.String(), // all Upper case Cosmos TX HEX, with no 0x prefix - ObservedExternalHeight: 0, - BallotIndex: "", - FinalizedZetaHeight: 0, - }, - OutboundParams: []*types.OutboundParams{ - { - Receiver: param.Erc20CustodyContractAddress, - ReceiverChainId: msg.ChainId, - CoinType: coin.CoinType_Cmd, - Amount: math.NewUint(0), - TssNonce: 0, - GasLimit: 100_000, - GasPrice: medianGasPrice.String(), - GasPriorityFee: priorityFee.String(), - Hash: "", - BallotIndex: "", - ObservedExternalHeight: 0, - TssPubkey: tss.TssPubkey, - }, - }, - } + // create the cctx + cctx := types.WhitelistERC20CmdCCTX( + msg.Creator, + zrc20Addr, + msg.Erc20Address, + params.Erc20CustodyContractAddress, + msg.ChainId, + medianGasPrice.String(), + priorityFee.String(), + tss.TssPubkey, + ) + err = k.SetObserverOutboundInfo(ctx, msg.ChainId, &cctx) if err != nil { return nil, err @@ -202,7 +161,7 @@ func (k msgServer) WhitelistERC20( err = ctx.EventManager().EmitTypedEvent( &types.EventERC20Whitelist{ Zrc20Address: zrc20Addr.Hex(), - WhitelistCctxIndex: index, + WhitelistCctxIndex: cctx.Index, }, ) if err != nil { @@ -211,6 +170,6 @@ func (k msgServer) WhitelistERC20( return &types.MsgWhitelistERC20Response{ Zrc20Address: zrc20Addr.Hex(), - CctxIndex: index, + CctxIndex: cctx.Index, }, nil } diff --git a/x/crosschain/types/cmd_cctxs.go b/x/crosschain/types/cmd_cctxs.go new file mode 100644 index 0000000000..51d4eaa9de --- /dev/null +++ b/x/crosschain/types/cmd_cctxs.go @@ -0,0 +1,260 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" + zetacrypto "github.com/zeta-chain/zetacore/pkg/crypto" + "github.com/zeta-chain/zetacore/pkg/gas" +) + +const ( + // TssMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the tss migration . + // This is done to avoid the tss migration tx getting stuck in the mempool + TssMigrationGasMultiplierEVM = "2.5" + + // TSSMigrationBufferAmountEVM is the buffer amount added to the gas price for the tss migration transaction + TSSMigrationBufferAmountEVM = "2100000000" + + // ERC20CustodyMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the erc20 custody migration + // NOTE: this is a integer type unlike type above because the message logic is slightly different and an integer is needed + ERC20CustodyMigrationGasMultiplierEVM = 2 + + // ERC20CustodyWhitelistGasMultiplierEVM is multiplied to the median gas price to get the gas price for the erc20 custody whitelist + ERC20CustodyWhitelistGasMultiplierEVM = 2 +) + +// MigrateERC20CustodyFundsCmdCCTX returns a CCTX allowing to migrate ERC20 custody funds +func MigrateERC20CustodyFundsCmdCCTX( + creator string, + erc20Address string, + custodyContractAddress string, + newCustodyContractAddress string, + chainID int64, + amount sdkmath.Uint, + gasPrice string, + priorityFee string, + tssPubKey string, + currentNonce uint64, +) CrossChainTx { + indexString := GetERC20CustodyMigrationCCTXIndexString(tssPubKey, currentNonce, erc20Address) + hash := crypto.Keccak256Hash([]byte(indexString)) + + return newCmdCCTX( + creator, + hash.Hex(), + fmt.Sprintf( + "%s:%s,%s,%s", + constant.CmdMigrateERC20CustodyFunds, + newCustodyContractAddress, + erc20Address, + amount.String(), + ), + creator, + hash.Hex(), + custodyContractAddress, + chainID, + sdkmath.NewUint(0), + 100_000, + gasPrice, + priorityFee, + tssPubKey, + ) +} + +// GetERC20CustodyMigrationCCTXIndexString returns the index string of the CCTX for migrating ERC20 custody funds +func GetERC20CustodyMigrationCCTXIndexString( + tssPubKey string, + nonce uint64, + erc20Address string, +) string { + return fmt.Sprintf("%s-%d-%s", tssPubKey, nonce, erc20Address) +} + +// WhitelistERC20CmdCCTX returns a CCTX allowing to whitelist an ERC20 token on an external chain +func WhitelistERC20CmdCCTX( + creator string, + zrc20Address ethcommon.Address, + erc20Address string, + custodyContractAddress string, + chainID int64, + gasPrice string, + priorityFee string, + tssPubKey string, +) CrossChainTx { + // calculate the cctx index + // we use the deployed zrc20 contract address to generate a unique index + // since other parts of the system may use the zrc20 for the index, we add a message specific suffix + hash := crypto.Keccak256Hash(zrc20Address.Bytes(), []byte("WhitelistERC20")) + + return newCmdCCTX( + creator, + hash.Hex(), + fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, erc20Address), + creator, + hash.Hex(), + custodyContractAddress, + chainID, + sdkmath.NewUint(0), + 100_000, + gasPrice, + priorityFee, + tssPubKey, + ) +} + +// MigrateFundCmdCCTX returns a CCTX allowing to migrate funds from the current TSS to the new TSS +func MigrateFundCmdCCTX( + blockHeight int64, + creator string, + inboundHash string, + chainID int64, + amount sdkmath.Uint, + medianGasPrice sdkmath.Uint, + priorityFee sdkmath.Uint, + currentTSSPubKey string, + newTSSPubKey string, + additionalStaticChainInfo []chains.Chain, +) (CrossChainTx, error) { + var ( + sender string + receiver string + gasLimit uint64 + gasPrice string + finalAmount sdkmath.Uint + ) + + // set sender, receiver, gas limit, gas price and final amount based on the chain + switch { + case chains.IsEVMChain(chainID, additionalStaticChainInfo): + ethAddressOld, err := zetacrypto.GetTssAddrEVM(currentTSSPubKey) + if err != nil { + return CrossChainTx{}, err + } + ethAddressNew, err := zetacrypto.GetTssAddrEVM(newTSSPubKey) + if err != nil { + return CrossChainTx{}, err + } + sender = ethAddressOld.String() + receiver = ethAddressNew.String() + gasLimit = gas.EVMSend + gasPriceUint, err := gas.MultiplyGasPrice(medianGasPrice, TssMigrationGasMultiplierEVM) + if err != nil { + return CrossChainTx{}, err + } + evmFee := sdkmath.NewUint(gasLimit). + Mul(gasPriceUint). + Add(sdkmath.NewUintFromString(TSSMigrationBufferAmountEVM)) + if evmFee.GT(amount) { + return CrossChainTx{}, errorsmod.Wrap( + ErrInsufficientFundsTssMigration, + fmt.Sprintf( + "insufficient funds to pay for gas fee, amount: %s, gas fee: %s, chainid: %d", + amount.String(), + evmFee.String(), + chainID, + ), + ) + } + gasPrice = gasPriceUint.String() + finalAmount = amount.Sub(evmFee) + case chains.IsBitcoinChain(chainID, additionalStaticChainInfo): + bitcoinNetParams, err := chains.BitcoinNetParamsFromChainID(chainID) + if err != nil { + return CrossChainTx{}, err + } + btcAddressOld, err := zetacrypto.GetTssAddrBTC(currentTSSPubKey, bitcoinNetParams) + if err != nil { + return CrossChainTx{}, err + } + btcAddressNew, err := zetacrypto.GetTssAddrBTC(newTSSPubKey, bitcoinNetParams) + if err != nil { + return CrossChainTx{}, err + } + sender = btcAddressOld + receiver = btcAddressNew + gasLimit = 1_000_000 + gasPrice = medianGasPrice.MulUint64(2).String() + finalAmount = amount + default: + return CrossChainTx{}, errorsmod.Wrap(ErrUnsupportedChain, fmt.Sprintf("chain %d is not supported", chainID)) + } + + indexString := GetTssMigrationCCTXIndexString(currentTSSPubKey, newTSSPubKey, chainID, amount, blockHeight) + hash := crypto.Keccak256Hash([]byte(indexString)) + + return newCmdCCTX( + creator, + hash.Hex(), + fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), + sender, + inboundHash, + receiver, + chainID, + finalAmount, + gasLimit, + gasPrice, + priorityFee.MulUint64(2).String(), + newTSSPubKey, + ), nil +} + +// GetTssMigrationCCTXIndexString returns the index string of the CCTX for migrating funds from the current TSS to the new TSS +func GetTssMigrationCCTXIndexString( + currentTssPubkey, + newTssPubkey string, + chainID int64, + amount sdkmath.Uint, + height int64, +) string { + return fmt.Sprintf("%s-%s-%d-%s-%d", currentTssPubkey, newTssPubkey, chainID, amount.String(), height) +} + +// newCmdCCTX returns a new CCTX for admin cmd with the given parameters +func newCmdCCTX( + creator string, + index string, + relayedMessage, + sender string, + inboundHash string, + receiver string, + chainID int64, + amount sdkmath.Uint, + gasLimit uint64, + medianGasPrice string, + priorityFee string, + tssPubKey string, +) CrossChainTx { + return CrossChainTx{ + Creator: creator, + Index: index, + RelayedMessage: relayedMessage, + CctxStatus: &Status{ + Status: CctxStatus_PendingOutbound, + }, + InboundParams: &InboundParams{ + Sender: sender, + CoinType: coin.CoinType_Cmd, + ObservedHash: inboundHash, + }, + OutboundParams: []*OutboundParams{ + { + Receiver: receiver, + ReceiverChainId: chainID, + CoinType: coin.CoinType_Cmd, + Amount: amount, + GasLimit: gasLimit, + GasPrice: medianGasPrice, + GasPriorityFee: priorityFee, + TssPubkey: tssPubKey, + }, + }, + } +} diff --git a/x/crosschain/types/cmd_cctxs_test.go b/x/crosschain/types/cmd_cctxs_test.go new file mode 100644 index 0000000000..d2a21300e2 --- /dev/null +++ b/x/crosschain/types/cmd_cctxs_test.go @@ -0,0 +1,540 @@ +package types_test + +import ( + sdkmath "cosmossdk.io/math" + "fmt" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/pkg/gas" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "testing" +) + +func TestMigrateERC20CustodyFundsCmdCCTX(t *testing.T) { + t.Run("returns a new CCTX for migrating ERC20 custody funds with unique index", func(t *testing.T) { + // ARRANGE + creator := sample.AccAddress() + erc20Address := sample.EthAddress().String() + custodyContractAddress := sample.EthAddress().String() + newCustodyContractAddress := sample.EthAddress().String() + chainID := int64(42) + amount := sdkmath.NewUint(1000) + gasPrice := "100000" + priorityFee := "100000" + tssPubKey := sample.PubKeyString() + currentNonce := uint64(1) + + // ACT + cctx := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + erc20Address, + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + tssPubKey, + currentNonce, + ) + cctxDifferentERC20Address := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + sample.EthAddress().String(), + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + tssPubKey, + currentNonce, + ) + cctxDifferentNonce := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + erc20Address, + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + tssPubKey, + currentNonce+1, + ) + cctxDifferentTSSPubkey := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + erc20Address, + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + sample.PubKeyString(), + currentNonce, + ) + + // ASSERT + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues(t, fmt.Sprintf("%s:%s,%s,1000", + constant.CmdMigrateERC20CustodyFunds, + newCustodyContractAddress, + erc20Address, + ), cctx.RelayedMessage) + require.EqualValues(t, creator, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues(t, custodyContractAddress, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chainID, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.EqualValues(t, sdkmath.NewUint(0), cctx.OutboundParams[0].Amount) + require.EqualValues(t, 100_000, cctx.OutboundParams[0].GasLimit) + require.EqualValues(t, gasPrice, cctx.OutboundParams[0].GasPrice) + require.EqualValues(t, priorityFee, cctx.OutboundParams[0].GasPriorityFee) + require.EqualValues(t, tssPubKey, cctx.OutboundParams[0].TssPubkey) + + // check erc20, TSS pubkey and nonce produce unique index + require.NotEqual(t, cctx.Index, cctxDifferentERC20Address.Index) + require.NotEqual(t, cctx.Index, cctxDifferentNonce.Index) + require.NotEqual(t, cctx.Index, cctxDifferentTSSPubkey.Index) + }) +} + +func TestGetERC20CustodyMigrationCCTXIndexString(t *testing.T) { + t.Run("returns the unique index string for the CCTX for migrating ERC20 custody funds", func(t *testing.T) { + // ARRANGE + tssPubKey := sample.PubKeyString() + nonce := uint64(1) + erc20Address := sample.EthAddress().String() + + // ACT + index := types.GetERC20CustodyMigrationCCTXIndexString( + tssPubKey, + nonce, + erc20Address, + ) + indexDifferentTSSPubkey := types.GetERC20CustodyMigrationCCTXIndexString( + sample.PubKeyString(), + nonce, + erc20Address, + ) + indexDifferentNonce := types.GetERC20CustodyMigrationCCTXIndexString( + tssPubKey, + nonce+1, + erc20Address, + ) + indexDifferentERC20Address := types.GetERC20CustodyMigrationCCTXIndexString( + tssPubKey, + nonce, + sample.EthAddress().String(), + ) + + // ASSERT + require.NotEmpty(t, index) + require.NotEqual(t, index, indexDifferentTSSPubkey) + require.NotEqual(t, index, indexDifferentNonce) + require.NotEqual(t, index, indexDifferentERC20Address) + }) +} + +func TestWhitelistERC20CmdCCTX(t *testing.T) { + t.Run("returns a new CCTX for whitelisting ERC20 tokens", func(t *testing.T) { + // ARRANGE + creator := sample.AccAddress() + zrc20Address := sample.EthAddress() + erc20Address := sample.EthAddress().Hex() + custodyAddress := sample.EthAddress().Hex() + chainID := int64(42) + gasPrice := "100000" + priorityFee := "100000" + tssPubKey := sample.PubKeyString() + + // ACT + cctx := types.WhitelistERC20CmdCCTX( + creator, + zrc20Address, + erc20Address, + custodyAddress, + chainID, + gasPrice, + priorityFee, + tssPubKey, + ) + cctxDifferentZRC20Address := types.WhitelistERC20CmdCCTX( + creator, + sample.EthAddress(), + erc20Address, + custodyAddress, + chainID, + gasPrice, + priorityFee, + tssPubKey, + ) + + // ASSERT + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues(t, fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, erc20Address), cctx.RelayedMessage) + require.EqualValues(t, creator, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues(t, custodyAddress, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chainID, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.EqualValues(t, sdkmath.NewUint(0), cctx.OutboundParams[0].Amount) + require.EqualValues(t, 100_000, cctx.OutboundParams[0].GasLimit) + require.EqualValues(t, gasPrice, cctx.OutboundParams[0].GasPrice) + require.EqualValues(t, priorityFee, cctx.OutboundParams[0].GasPriorityFee) + require.EqualValues(t, tssPubKey, cctx.OutboundParams[0].TssPubkey) + + // check zrc20 address produces unique index + require.NotEqual(t, cctx.Index, cctxDifferentZRC20Address.Index) + }) +} + +func TestMigrateFundCmdCCTX(t *testing.T) { + t.Run("returns a new CCTX for migrating funds on EVM", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + cctx, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.NoError(t, err) + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues( + t, + fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), + cctx.RelayedMessage, + ) + require.NotEmpty(t, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.NotEmpty(t, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chains.Ethereum.ChainId, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.False(t, cctx.OutboundParams[0].Amount.IsZero()) + require.EqualValues(t, gas.EVMSend, cctx.OutboundParams[0].GasLimit) + require.NotEmpty(t, cctx.OutboundParams[0].GasPrice) + require.NotEmpty(t, cctx.OutboundParams[0].GasPriorityFee) + }) + + t.Run("returns a new CCTX for migrating funds on Bitcoin", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.BitcoinMainnet.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + cctx, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.NoError(t, err) + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues( + t, + fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), + cctx.RelayedMessage, + ) + require.NotEmpty(t, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.NotEmpty(t, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chains.BitcoinMainnet.ChainId, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.False(t, cctx.OutboundParams[0].Amount.IsZero()) + require.EqualValues(t, uint64(1_000_000), cctx.OutboundParams[0].GasLimit) + require.NotEmpty(t, cctx.OutboundParams[0].GasPrice) + require.NotEmpty(t, cctx.OutboundParams[0].GasPriorityFee) + }) + + t.Run("prevent migration with invalid ETH address for current TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := "invalid" + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration with invalid ETH address for new TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := "invalid" + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration on EVM if fees higher than amount", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(100_000_000) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration with invalid Bitcoin address for current TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.BitcoinMainnet.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := "invalid" + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration with invalid Bitcoin address for new TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.BitcoinMainnet.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := "invalid" + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration if invalid chain ID", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := int64(1000) + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) +} + +func TestGetTssMigrationCCTXIndexString(t *testing.T) { + t.Run("returns unique index string for the CCTX for migrating funds", func(t *testing.T) { + // ARRANGE + currentTSSPubkey := sample.PubKeyString() + newTSSPubkey := sample.PubKeyString() + chainID := int64(42) + amount := sdkmath.NewUint(1000) + height := int64(1000) + + // ACT + index := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID, + amount, + height, + ) + indexDifferentCurrentTSSPubkey := types.GetTssMigrationCCTXIndexString( + sample.PubKeyString(), + newTSSPubkey, + chainID, + amount, + height, + ) + indexDifferentNewTSSPubkey := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + sample.PubKeyString(), + chainID, + amount, + height, + ) + indexDifferentChainID := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID+1, + amount, + height, + ) + indexDifferentAmount := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID, + sdkmath.NewUint(1001), + height, + ) + indexDifferentHeight := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID, + amount, + height+1, + ) + + // ASSERT + require.NotEmpty(t, index) + require.NotEqual(t, index, indexDifferentCurrentTSSPubkey) + require.NotEqual(t, index, indexDifferentNewTSSPubkey) + require.NotEqual(t, index, indexDifferentChainID) + require.NotEqual(t, index, indexDifferentAmount) + require.NotEqual(t, index, indexDifferentHeight) + }) +} diff --git a/x/crosschain/types/events.pb.go b/x/crosschain/types/events.pb.go index ff1dd01b62..133b88f884 100644 --- a/x/crosschain/types/events.pb.go +++ b/x/crosschain/types/events.pb.go @@ -619,6 +619,74 @@ func (m *EventERC20Whitelist) GetZrc20Address() string { return "" } +type EventERC20CustodyFundsMigration struct { + NewCustodyAddress string `protobuf:"bytes,1,opt,name=new_custody_address,json=newCustodyAddress,proto3" json:"new_custody_address,omitempty"` + Erc20Address string `protobuf:"bytes,2,opt,name=erc20_address,json=erc20Address,proto3" json:"erc20_address,omitempty"` + Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + CctxIndex string `protobuf:"bytes,4,opt,name=cctx_index,json=cctxIndex,proto3" json:"cctx_index,omitempty"` +} + +func (m *EventERC20CustodyFundsMigration) Reset() { *m = EventERC20CustodyFundsMigration{} } +func (m *EventERC20CustodyFundsMigration) String() string { return proto.CompactTextString(m) } +func (*EventERC20CustodyFundsMigration) ProtoMessage() {} +func (*EventERC20CustodyFundsMigration) Descriptor() ([]byte, []int) { + return fileDescriptor_dd08b628129fa2e1, []int{7} +} +func (m *EventERC20CustodyFundsMigration) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventERC20CustodyFundsMigration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventERC20CustodyFundsMigration.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventERC20CustodyFundsMigration) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventERC20CustodyFundsMigration.Merge(m, src) +} +func (m *EventERC20CustodyFundsMigration) XXX_Size() int { + return m.Size() +} +func (m *EventERC20CustodyFundsMigration) XXX_DiscardUnknown() { + xxx_messageInfo_EventERC20CustodyFundsMigration.DiscardUnknown(m) +} + +var xxx_messageInfo_EventERC20CustodyFundsMigration proto.InternalMessageInfo + +func (m *EventERC20CustodyFundsMigration) GetNewCustodyAddress() string { + if m != nil { + return m.NewCustodyAddress + } + return "" +} + +func (m *EventERC20CustodyFundsMigration) GetErc20Address() string { + if m != nil { + return m.Erc20Address + } + return "" +} + +func (m *EventERC20CustodyFundsMigration) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *EventERC20CustodyFundsMigration) GetCctxIndex() string { + if m != nil { + return m.CctxIndex + } + return "" +} + func init() { proto.RegisterType((*EventInboundFinalized)(nil), "zetachain.zetacore.crosschain.EventInboundFinalized") proto.RegisterType((*EventZrcWithdrawCreated)(nil), "zetachain.zetacore.crosschain.EventZrcWithdrawCreated") @@ -627,6 +695,7 @@ func init() { proto.RegisterType((*EventOutboundSuccess)(nil), "zetachain.zetacore.crosschain.EventOutboundSuccess") proto.RegisterType((*EventCCTXGasPriceIncreased)(nil), "zetachain.zetacore.crosschain.EventCCTXGasPriceIncreased") proto.RegisterType((*EventERC20Whitelist)(nil), "zetachain.zetacore.crosschain.EventERC20Whitelist") + proto.RegisterType((*EventERC20CustodyFundsMigration)(nil), "zetachain.zetacore.crosschain.EventERC20CustodyFundsMigration") } func init() { @@ -634,50 +703,54 @@ func init() { } var fileDescriptor_dd08b628129fa2e1 = []byte{ - // 686 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x95, 0xd1, 0x4e, 0x13, 0x4f, - 0x14, 0xc6, 0x59, 0x68, 0x4b, 0x3b, 0xb4, 0xfd, 0xff, 0x33, 0x56, 0x5d, 0x49, 0x68, 0xa0, 0xc6, - 0x68, 0x8c, 0x16, 0xc4, 0x27, 0x90, 0x06, 0x84, 0x18, 0x83, 0x01, 0x0c, 0x86, 0x9b, 0xc9, 0x74, - 0xe7, 0xb8, 0x3b, 0x71, 0xbb, 0xd3, 0xcc, 0xcc, 0xd2, 0xc2, 0x53, 0x18, 0x1f, 0xc5, 0xc4, 0x3b, - 0x1f, 0xc0, 0x4b, 0x2e, 0xbd, 0x34, 0xf4, 0x45, 0xcc, 0xcc, 0xec, 0x16, 0xda, 0x1a, 0xbd, 0x30, - 0x9a, 0x78, 0xb7, 0xe7, 0x3b, 0x67, 0xa6, 0xbf, 0xf9, 0xbe, 0xed, 0x0e, 0x7a, 0x78, 0x0e, 0x9a, - 0x06, 0x11, 0xe5, 0xc9, 0xba, 0x7d, 0x12, 0x12, 0xd6, 0x03, 0x29, 0x94, 0x72, 0x1a, 0x9c, 0x42, - 0xa2, 0x55, 0xbb, 0x2f, 0x85, 0x16, 0x78, 0x65, 0x3c, 0xdb, 0xce, 0x67, 0xdb, 0x57, 0xb3, 0xcb, - 0x8d, 0x50, 0x84, 0xc2, 0x4e, 0xae, 0x9b, 0x27, 0xb7, 0xa8, 0x35, 0x5a, 0x40, 0x37, 0xb7, 0xcd, - 0x2e, 0x7b, 0x49, 0x57, 0xa4, 0x09, 0xdb, 0xe1, 0x09, 0x8d, 0xf9, 0x39, 0x30, 0xbc, 0x8a, 0xaa, - 0x3d, 0x15, 0x12, 0x7d, 0xd6, 0x07, 0x92, 0xca, 0xd8, 0xf7, 0x56, 0xbd, 0x07, 0x95, 0x03, 0xd4, - 0x53, 0xe1, 0xd1, 0x59, 0x1f, 0x5e, 0xcb, 0x18, 0xaf, 0x20, 0x14, 0x04, 0x7a, 0x48, 0x78, 0xc2, - 0x60, 0xe8, 0xcf, 0xdb, 0x7e, 0xc5, 0x28, 0x7b, 0x46, 0xc0, 0xb7, 0x50, 0x49, 0x41, 0xc2, 0x40, - 0xfa, 0x0b, 0xb6, 0x95, 0x55, 0xf8, 0x0e, 0x2a, 0xeb, 0x21, 0x11, 0x32, 0xe4, 0x89, 0x5f, 0xb0, - 0x9d, 0x45, 0x3d, 0xdc, 0x37, 0x25, 0x6e, 0xa0, 0x22, 0x55, 0x0a, 0xb4, 0x5f, 0xb4, 0xba, 0x2b, - 0xf0, 0x1a, 0xaa, 0x72, 0x47, 0x47, 0x22, 0xaa, 0x22, 0xbf, 0x64, 0x9b, 0x4b, 0x99, 0xb6, 0x4b, - 0x55, 0x84, 0x37, 0x50, 0x23, 0x1f, 0xe9, 0xc6, 0x22, 0x78, 0x47, 0x22, 0xe0, 0x61, 0xa4, 0xfd, - 0x45, 0x3b, 0x8a, 0xb3, 0xde, 0x96, 0x69, 0xed, 0xda, 0x0e, 0x5e, 0x46, 0x65, 0x09, 0x01, 0xf0, - 0x53, 0x90, 0x7e, 0xd9, 0x4e, 0x8d, 0x6b, 0x7c, 0x0f, 0xd5, 0xf3, 0x67, 0x62, 0xcd, 0xf3, 0x2b, - 0x76, 0xa2, 0x96, 0xab, 0x1d, 0x23, 0x9a, 0x03, 0xd2, 0x9e, 0x48, 0x13, 0xed, 0x23, 0x77, 0x40, - 0x57, 0xe1, 0xfb, 0xe8, 0x3f, 0x09, 0x31, 0x3d, 0x03, 0x46, 0x7a, 0xa0, 0x14, 0x0d, 0xc1, 0x5f, - 0xb2, 0x03, 0xf5, 0x4c, 0x7e, 0xe9, 0x54, 0x63, 0x60, 0x02, 0x03, 0xa2, 0x34, 0xd5, 0xa9, 0xf2, - 0xab, 0xce, 0xc0, 0x04, 0x06, 0x87, 0x56, 0x30, 0x18, 0xae, 0x35, 0xde, 0xa6, 0xe6, 0x30, 0x9c, - 0x9a, 0xef, 0xb2, 0x86, 0xaa, 0xce, 0xd9, 0x8c, 0xb5, 0xee, 0xec, 0x71, 0x9a, 0x25, 0x6d, 0x7d, - 0x9c, 0x47, 0xb7, 0x6d, 0xca, 0x27, 0x32, 0x38, 0xe6, 0x3a, 0x62, 0x92, 0x0e, 0x3a, 0x12, 0xa8, - 0xfe, 0x93, 0x39, 0x4f, 0x73, 0x15, 0x66, 0xb8, 0x66, 0x92, 0x2d, 0xce, 0x26, 0x7b, 0x3d, 0xa7, - 0xd2, 0x2f, 0x73, 0x5a, 0xfc, 0x79, 0x4e, 0xe5, 0x89, 0x9c, 0x26, 0xed, 0xaf, 0x4c, 0xd9, 0xdf, - 0xfa, 0xe4, 0x21, 0xdf, 0x99, 0x06, 0x9a, 0xfe, 0x4d, 0xd7, 0x26, 0x2c, 0x29, 0xcc, 0x5a, 0x32, - 0xc9, 0x5d, 0x9c, 0xe6, 0xfe, 0xec, 0xa1, 0x86, 0xe5, 0xde, 0x4f, 0xb5, 0xfb, 0x4f, 0x53, 0x1e, - 0xa7, 0x12, 0x7e, 0x9f, 0x79, 0x05, 0x21, 0x11, 0xb3, 0xfc, 0x87, 0x1d, 0x77, 0x45, 0xc4, 0x2c, - 0x7b, 0x5f, 0x27, 0xb9, 0x0a, 0x3f, 0x78, 0x9d, 0x4f, 0x69, 0x9c, 0x02, 0xc9, 0xd2, 0x61, 0x19, - 0x7a, 0xcd, 0xaa, 0x07, 0x99, 0x38, 0x8b, 0x7f, 0x98, 0x06, 0x01, 0x28, 0xf5, 0x8f, 0xe0, 0x7f, - 0xf0, 0xd0, 0xb2, 0xc5, 0xef, 0x74, 0x8e, 0xde, 0x3c, 0xa7, 0xea, 0x95, 0xe4, 0x01, 0xec, 0x25, - 0x81, 0x04, 0xaa, 0x80, 0x4d, 0x21, 0x7a, 0xd3, 0x88, 0x8f, 0x10, 0x0e, 0xa9, 0x22, 0x7d, 0xb3, - 0x88, 0xf0, 0x6c, 0x55, 0x76, 0x92, 0xff, 0xc3, 0xa9, 0xdd, 0xcc, 0x87, 0x86, 0x32, 0xc6, 0x35, - 0x17, 0x09, 0x8d, 0xc9, 0x5b, 0x80, 0xfc, 0x54, 0xf5, 0x2b, 0x79, 0x07, 0x40, 0xb5, 0x62, 0x74, - 0xc3, 0x32, 0x6d, 0x1f, 0x74, 0x36, 0x37, 0x8e, 0x23, 0xae, 0x21, 0xe6, 0x4a, 0x9b, 0xaf, 0xe6, - 0x20, 0x2f, 0xc8, 0x0c, 0x16, 0x1e, 0xf7, 0x3a, 0x63, 0xbe, 0xbb, 0xa8, 0x76, 0x2e, 0x83, 0xcd, - 0x0d, 0x42, 0x19, 0x93, 0xa0, 0x54, 0x86, 0x56, 0xb5, 0xe2, 0x33, 0xa7, 0x6d, 0xbd, 0xf8, 0x72, - 0xd9, 0xf4, 0x2e, 0x2e, 0x9b, 0xde, 0xb7, 0xcb, 0xa6, 0xf7, 0x7e, 0xd4, 0x9c, 0xbb, 0x18, 0x35, - 0xe7, 0xbe, 0x8e, 0x9a, 0x73, 0x27, 0x4f, 0x42, 0xae, 0xa3, 0xb4, 0xdb, 0x0e, 0x44, 0xcf, 0xde, - 0x67, 0x8f, 0xa7, 0xae, 0xb6, 0xe1, 0xf5, 0xcb, 0xcd, 0x04, 0xad, 0xba, 0x25, 0x7b, 0x4f, 0x3d, - 0xfd, 0x1e, 0x00, 0x00, 0xff, 0xff, 0xc4, 0x44, 0xd3, 0x7e, 0x0a, 0x07, 0x00, 0x00, + // 752 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x95, 0x41, 0x4f, 0xdb, 0x48, + 0x14, 0xc7, 0x31, 0x24, 0x21, 0x19, 0x92, 0xec, 0xae, 0xc9, 0xee, 0x7a, 0x91, 0xc8, 0x42, 0x56, + 0xab, 0x5d, 0xad, 0xb6, 0x81, 0xd2, 0x4f, 0x50, 0x22, 0x28, 0xa8, 0x42, 0x54, 0x40, 0x45, 0xc5, + 0xc5, 0x9a, 0x78, 0x5e, 0xed, 0x51, 0x9d, 0x99, 0x68, 0x66, 0x4c, 0x12, 0x3e, 0x45, 0xd5, 0xef, + 0xd1, 0x4b, 0xa5, 0xde, 0xfa, 0x01, 0x7a, 0xe4, 0xd8, 0x63, 0x45, 0xbe, 0x48, 0x35, 0x33, 0x76, + 0x48, 0x1c, 0xd4, 0x1e, 0xaa, 0x56, 0xea, 0xcd, 0xf3, 0x7f, 0x6f, 0xde, 0xfb, 0xcd, 0xff, 0xd9, + 0x1e, 0xf4, 0xdf, 0x15, 0x28, 0x1c, 0x44, 0x98, 0xb2, 0x2d, 0xf3, 0xc4, 0x05, 0x6c, 0x05, 0x82, + 0x4b, 0x69, 0x35, 0xb8, 0x04, 0xa6, 0x64, 0xbb, 0x2f, 0xb8, 0xe2, 0xee, 0xfa, 0x24, 0xb7, 0x9d, + 0xe5, 0xb6, 0x6f, 0x73, 0xd7, 0x1a, 0x21, 0x0f, 0xb9, 0xc9, 0xdc, 0xd2, 0x4f, 0x76, 0x53, 0x6b, + 0xbc, 0x84, 0x7e, 0xdd, 0xd3, 0x55, 0x0e, 0x59, 0x97, 0x27, 0x8c, 0xec, 0x53, 0x86, 0x63, 0x7a, + 0x05, 0xc4, 0xdd, 0x40, 0xd5, 0x9e, 0x0c, 0x7d, 0x35, 0xea, 0x83, 0x9f, 0x88, 0xd8, 0x73, 0x36, + 0x9c, 0x7f, 0x2b, 0x27, 0xa8, 0x27, 0xc3, 0xb3, 0x51, 0x1f, 0x9e, 0x8a, 0xd8, 0x5d, 0x47, 0x28, + 0x08, 0xd4, 0xd0, 0xa7, 0x8c, 0xc0, 0xd0, 0x5b, 0x34, 0xf1, 0x8a, 0x56, 0x0e, 0xb5, 0xe0, 0xfe, + 0x86, 0x4a, 0x12, 0x18, 0x01, 0xe1, 0x2d, 0x99, 0x50, 0xba, 0x72, 0xff, 0x40, 0x65, 0x35, 0xf4, + 0xb9, 0x08, 0x29, 0xf3, 0x0a, 0x26, 0xb2, 0xac, 0x86, 0xc7, 0x7a, 0xe9, 0x36, 0x50, 0x11, 0x4b, + 0x09, 0xca, 0x2b, 0x1a, 0xdd, 0x2e, 0xdc, 0x4d, 0x54, 0xa5, 0x96, 0xce, 0x8f, 0xb0, 0x8c, 0xbc, + 0x92, 0x09, 0xae, 0xa4, 0xda, 0x01, 0x96, 0x91, 0xbb, 0x8d, 0x1a, 0x59, 0x4a, 0x37, 0xe6, 0xc1, + 0x0b, 0x3f, 0x02, 0x1a, 0x46, 0xca, 0x5b, 0x36, 0xa9, 0x6e, 0x1a, 0xdb, 0xd5, 0xa1, 0x03, 0x13, + 0x71, 0xd7, 0x50, 0x59, 0x40, 0x00, 0xf4, 0x12, 0x84, 0x57, 0x36, 0x59, 0x93, 0xb5, 0xfb, 0x37, + 0xaa, 0x67, 0xcf, 0xbe, 0x31, 0xcf, 0xab, 0x98, 0x8c, 0x5a, 0xa6, 0x76, 0xb4, 0xa8, 0x0f, 0x88, + 0x7b, 0x3c, 0x61, 0xca, 0x43, 0xf6, 0x80, 0x76, 0xe5, 0xfe, 0x83, 0x7e, 0x12, 0x10, 0xe3, 0x11, + 0x10, 0xbf, 0x07, 0x52, 0xe2, 0x10, 0xbc, 0x15, 0x93, 0x50, 0x4f, 0xe5, 0x23, 0xab, 0x6a, 0x03, + 0x19, 0x0c, 0x7c, 0xa9, 0xb0, 0x4a, 0xa4, 0x57, 0xb5, 0x06, 0x32, 0x18, 0x9c, 0x1a, 0x41, 0x63, + 0xd8, 0xd0, 0xa4, 0x4c, 0xcd, 0x62, 0x58, 0x35, 0xab, 0xb2, 0x89, 0xaa, 0xd6, 0xd9, 0x94, 0xb5, + 0x6e, 0xed, 0xb1, 0x9a, 0x21, 0x6d, 0xbd, 0x59, 0x44, 0xbf, 0x9b, 0x29, 0x5f, 0x88, 0xe0, 0x9c, + 0xaa, 0x88, 0x08, 0x3c, 0xe8, 0x08, 0xc0, 0xea, 0x5b, 0xce, 0x39, 0xcf, 0x55, 0x98, 0xe3, 0x9a, + 0x9b, 0x6c, 0x71, 0x7e, 0xb2, 0xd3, 0x73, 0x2a, 0x7d, 0x71, 0x4e, 0xcb, 0x9f, 0x9f, 0x53, 0x79, + 0x66, 0x4e, 0xb3, 0xf6, 0x57, 0x72, 0xf6, 0xb7, 0xde, 0x3a, 0xc8, 0xb3, 0xa6, 0x81, 0xc2, 0xdf, + 0xd3, 0xb5, 0x19, 0x4b, 0x0a, 0xf3, 0x96, 0xcc, 0x72, 0x17, 0xf3, 0xdc, 0xef, 0x1c, 0xd4, 0x30, + 0xdc, 0xc7, 0x89, 0xb2, 0xdf, 0x34, 0xa6, 0x71, 0x22, 0xe0, 0xeb, 0x99, 0xd7, 0x11, 0xe2, 0x31, + 0xc9, 0x1a, 0x5b, 0xee, 0x0a, 0x8f, 0x49, 0xfa, 0xbe, 0xce, 0x72, 0x15, 0xee, 0x78, 0x9d, 0x2f, + 0x71, 0x9c, 0x80, 0x9f, 0x4e, 0x87, 0xa4, 0xe8, 0x35, 0xa3, 0x9e, 0xa4, 0xe2, 0x3c, 0xfe, 0x69, + 0x12, 0x04, 0x20, 0xe5, 0x0f, 0x82, 0xff, 0xca, 0x41, 0x6b, 0x06, 0xbf, 0xd3, 0x39, 0x7b, 0xf6, + 0x08, 0xcb, 0x27, 0x82, 0x06, 0x70, 0xc8, 0x02, 0x01, 0x58, 0x02, 0xc9, 0x21, 0x3a, 0x79, 0xc4, + 0xff, 0x91, 0x1b, 0x62, 0xe9, 0xf7, 0xf5, 0x26, 0x9f, 0xa6, 0xbb, 0xd2, 0x93, 0xfc, 0x1c, 0xe6, + 0xaa, 0xe9, 0x1f, 0x0d, 0x26, 0x84, 0x2a, 0xca, 0x19, 0x8e, 0xfd, 0xe7, 0x00, 0xd9, 0xa9, 0xea, + 0xb7, 0xf2, 0x3e, 0x80, 0x6c, 0xc5, 0x68, 0xd5, 0x30, 0xed, 0x9d, 0x74, 0x76, 0xb6, 0xcf, 0x23, + 0xaa, 0x20, 0xa6, 0x52, 0xe9, 0xbf, 0xe6, 0x20, 0x5b, 0xf8, 0x73, 0x58, 0xee, 0x24, 0xd6, 0x99, + 0xf0, 0xfd, 0x85, 0x6a, 0x57, 0x22, 0xd8, 0xd9, 0xf6, 0x31, 0x21, 0x02, 0xa4, 0x4c, 0xd1, 0xaa, + 0x46, 0x7c, 0x68, 0xb5, 0xd6, 0x6b, 0x07, 0xfd, 0x79, 0xdb, 0xae, 0x93, 0x48, 0xc5, 0xc9, 0x68, + 0x3f, 0x61, 0x44, 0x1e, 0xd1, 0x50, 0x60, 0xcd, 0xe5, 0xb6, 0xd1, 0xaa, 0x36, 0x3b, 0xb0, 0xc1, + 0x49, 0x39, 0xdb, 0xf9, 0x17, 0x06, 0x83, 0x74, 0x5b, 0x5a, 0x53, 0x37, 0x86, 0xbb, 0x1a, 0xc3, + 0x54, 0xe3, 0xa9, 0x0f, 0x7d, 0x29, 0xff, 0xa1, 0x4f, 0x9d, 0xae, 0x90, 0x33, 0x7d, 0xf7, 0xf1, + 0xfb, 0x9b, 0xa6, 0x73, 0x7d, 0xd3, 0x74, 0x3e, 0xde, 0x34, 0x9d, 0x97, 0xe3, 0xe6, 0xc2, 0xf5, + 0xb8, 0xb9, 0xf0, 0x61, 0xdc, 0x5c, 0xb8, 0xb8, 0x1f, 0x52, 0x15, 0x25, 0xdd, 0x76, 0xc0, 0x7b, + 0xe6, 0xfe, 0xbd, 0x97, 0xbb, 0x8a, 0x87, 0xd3, 0x97, 0xb1, 0x7e, 0x31, 0x65, 0xb7, 0x64, 0xee, + 0xd5, 0x07, 0x9f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x99, 0x3d, 0xe9, 0xec, 0xba, 0x07, 0x00, 0x00, } func (m *EventInboundFinalized) Marshal() (dAtA []byte, err error) { @@ -1142,6 +1215,57 @@ func (m *EventERC20Whitelist) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *EventERC20CustodyFundsMigration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventERC20CustodyFundsMigration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventERC20CustodyFundsMigration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CctxIndex) > 0 { + i -= len(m.CctxIndex) + copy(dAtA[i:], m.CctxIndex) + i = encodeVarintEvents(dAtA, i, uint64(len(m.CctxIndex))) + i-- + dAtA[i] = 0x22 + } + if len(m.Amount) > 0 { + i -= len(m.Amount) + copy(dAtA[i:], m.Amount) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Amount))) + i-- + dAtA[i] = 0x1a + } + if len(m.Erc20Address) > 0 { + i -= len(m.Erc20Address) + copy(dAtA[i:], m.Erc20Address) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Erc20Address))) + i-- + dAtA[i] = 0x12 + } + if len(m.NewCustodyAddress) > 0 { + i -= len(m.NewCustodyAddress) + copy(dAtA[i:], m.NewCustodyAddress) + i = encodeVarintEvents(dAtA, i, uint64(len(m.NewCustodyAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -1388,6 +1512,31 @@ func (m *EventERC20Whitelist) Size() (n int) { return n } +func (m *EventERC20CustodyFundsMigration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.NewCustodyAddress) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Erc20Address) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Amount) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.CctxIndex) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3120,6 +3269,184 @@ func (m *EventERC20Whitelist) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventERC20CustodyFundsMigration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventERC20CustodyFundsMigration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventERC20CustodyFundsMigration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewCustodyAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewCustodyAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Erc20Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Erc20Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Amount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxIndex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxIndex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipEvents(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/crosschain/types/keys.go b/x/crosschain/types/keys.go index 8496ccf608..d9fea4a77d 100644 --- a/x/crosschain/types/keys.go +++ b/x/crosschain/types/keys.go @@ -25,12 +25,6 @@ const ( ProtocolFee = 2000000000000000000 - // TssMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the tss migration . - // This is done to avoid the tss migration tx getting stuck in the mempool - TssMigrationGasMultiplierEVM = "2.5" - // TSSMigrationBufferAmountEVM is the buffer amount added to the gas price for the tss migration transaction - TSSMigrationBufferAmountEVM = "2100000000" - // CCTXIndexLength is the length of a crosschain transaction index CCTXIndexLength = 66 ) diff --git a/x/crosschain/types/message_migrate_erc20_custody_funds.go b/x/crosschain/types/message_migrate_erc20_custody_funds.go new file mode 100644 index 0000000000..da69db1fd8 --- /dev/null +++ b/x/crosschain/types/message_migrate_erc20_custody_funds.go @@ -0,0 +1,68 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + ethcommon "github.com/ethereum/go-ethereum/common" +) + +const TypeMsgMigrateERC20CustodyFunds = "MigrateERC20CustodyFunds" + +var _ sdk.Msg = &MsgMigrateERC20CustodyFunds{} + +func NewMsgMigrateERC20CustodyFunds( + creator string, + chainID int64, + newCustodyAddress string, + erc20Address string, + amount sdkmath.Uint, +) *MsgMigrateERC20CustodyFunds { + return &MsgMigrateERC20CustodyFunds{ + Creator: creator, + ChainId: chainID, + NewCustodyAddress: newCustodyAddress, + Erc20Address: erc20Address, + Amount: amount, + } +} + +func (msg *MsgMigrateERC20CustodyFunds) Route() string { + return RouterKey +} + +func (msg *MsgMigrateERC20CustodyFunds) Type() string { + return TypeMsgMigrateERC20CustodyFunds +} + +func (msg *MsgMigrateERC20CustodyFunds) GetSigners() []sdk.AccAddress { + creator, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + panic(err) + } + return []sdk.AccAddress{creator} +} + +func (msg *MsgMigrateERC20CustodyFunds) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgMigrateERC20CustodyFunds) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + } + + switch { + case !ethcommon.IsHexAddress(msg.NewCustodyAddress): + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid new custody address") + case !ethcommon.IsHexAddress(msg.Erc20Address): + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid erc20 address") + case msg.Amount.IsZero(): + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "amount cannot be zero") + } + + return nil +} diff --git a/x/crosschain/types/message_migrate_erc20_custody_funds_test.go b/x/crosschain/types/message_migrate_erc20_custody_funds_test.go new file mode 100644 index 0000000000..9a86b6d141 --- /dev/null +++ b/x/crosschain/types/message_migrate_erc20_custody_funds_test.go @@ -0,0 +1,169 @@ +package types_test + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestNewMsgMigrateERC20CustodyFunds_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) + tests := []struct { + name string + msg *types.MsgMigrateERC20CustodyFunds + error bool + }{ + { + name: "invalid creator", + msg: types.NewMsgMigrateERC20CustodyFunds( + "invalid address", + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + sample.EthAddress().String(), + sdkmath.NewUintFromString("100000"), + ), + error: true, + }, + { + name: "invalid amount", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + sample.EthAddress().String(), + sdkmath.NewUintFromString("0"), + ), + error: true, + }, + { + name: "valid msg", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + sample.EthAddress().String(), + sdkmath.NewUintFromString("100000"), + ), + }, + { + name: "invalid erc20 address", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + "invalid address", + sdkmath.NewUintFromString("100000"), + ), + error: true, + }, + { + name: "invalid new custody address", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + "invalid address", + sample.EthAddress().String(), + sdkmath.NewUintFromString("100000"), + ), + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.error { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNewMsgMigrateERC20CustodyFunds_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgMigrateERC20CustodyFunds + panics bool + }{ + { + name: "valid signer", + msg: types.MsgMigrateERC20CustodyFunds{ + Creator: signer, + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgMigrateERC20CustodyFunds{ + Creator: "invalid_address", + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestNewMsgMigrateERC20CustodyFunds_Type(t *testing.T) { + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + } + require.Equal(t, types.TypeMsgMigrateERC20CustodyFunds, msg.Type()) +} + +func TestNewMsgMigrateERC20CustodyFunds_Route(t *testing.T) { + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestNewMsgMigrateERC20CustodyFunds_GetSignBytes(t *testing.T) { + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/tx.pb.go b/x/crosschain/types/tx.pb.go index 4bd154f595..55be051cae 100644 --- a/x/crosschain/types/tx.pb.go +++ b/x/crosschain/types/tx.pb.go @@ -1431,6 +1431,119 @@ func (m *MsgUpdateRateLimiterFlagsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateRateLimiterFlagsResponse proto.InternalMessageInfo +type MsgMigrateERC20CustodyFunds struct { + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + ChainId int64 `protobuf:"varint,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + NewCustodyAddress string `protobuf:"bytes,3,opt,name=new_custody_address,json=newCustodyAddress,proto3" json:"new_custody_address,omitempty"` + Erc20Address string `protobuf:"bytes,4,opt,name=erc20_address,json=erc20Address,proto3" json:"erc20_address,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,5,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"amount"` +} + +func (m *MsgMigrateERC20CustodyFunds) Reset() { *m = MsgMigrateERC20CustodyFunds{} } +func (m *MsgMigrateERC20CustodyFunds) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateERC20CustodyFunds) ProtoMessage() {} +func (*MsgMigrateERC20CustodyFunds) Descriptor() ([]byte, []int) { + return fileDescriptor_15f0860550897740, []int{24} +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateERC20CustodyFunds.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateERC20CustodyFunds.Merge(m, src) +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateERC20CustodyFunds) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateERC20CustodyFunds.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateERC20CustodyFunds proto.InternalMessageInfo + +func (m *MsgMigrateERC20CustodyFunds) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +func (m *MsgMigrateERC20CustodyFunds) GetChainId() int64 { + if m != nil { + return m.ChainId + } + return 0 +} + +func (m *MsgMigrateERC20CustodyFunds) GetNewCustodyAddress() string { + if m != nil { + return m.NewCustodyAddress + } + return "" +} + +func (m *MsgMigrateERC20CustodyFunds) GetErc20Address() string { + if m != nil { + return m.Erc20Address + } + return "" +} + +type MsgMigrateERC20CustodyFundsResponse struct { + CctxIndex string `protobuf:"bytes,1,opt,name=cctx_index,json=cctxIndex,proto3" json:"cctx_index,omitempty"` +} + +func (m *MsgMigrateERC20CustodyFundsResponse) Reset() { *m = MsgMigrateERC20CustodyFundsResponse{} } +func (m *MsgMigrateERC20CustodyFundsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateERC20CustodyFundsResponse) ProtoMessage() {} +func (*MsgMigrateERC20CustodyFundsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15f0860550897740, []int{25} +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse.Merge(m, src) +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse proto.InternalMessageInfo + +func (m *MsgMigrateERC20CustodyFundsResponse) GetCctxIndex() string { + if m != nil { + return m.CctxIndex + } + return "" +} + func init() { proto.RegisterType((*MsgMigrateTssFunds)(nil), "zetachain.zetacore.crosschain.MsgMigrateTssFunds") proto.RegisterType((*MsgMigrateTssFundsResponse)(nil), "zetachain.zetacore.crosschain.MsgMigrateTssFundsResponse") @@ -1456,6 +1569,8 @@ func init() { proto.RegisterType((*MsgRefundAbortedCCTXResponse)(nil), "zetachain.zetacore.crosschain.MsgRefundAbortedCCTXResponse") proto.RegisterType((*MsgUpdateRateLimiterFlags)(nil), "zetachain.zetacore.crosschain.MsgUpdateRateLimiterFlags") proto.RegisterType((*MsgUpdateRateLimiterFlagsResponse)(nil), "zetachain.zetacore.crosschain.MsgUpdateRateLimiterFlagsResponse") + proto.RegisterType((*MsgMigrateERC20CustodyFunds)(nil), "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds") + proto.RegisterType((*MsgMigrateERC20CustodyFundsResponse)(nil), "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFundsResponse") } func init() { @@ -1463,105 +1578,110 @@ func init() { } var fileDescriptor_15f0860550897740 = []byte{ - // 1557 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0x36, 0x8e, 0x63, 0x3f, 0xc7, 0x4e, 0xba, 0xa4, 0xa9, 0xb3, 0x69, 0x9c, 0xd4, 0xa5, - 0x21, 0x42, 0xad, 0x9d, 0xba, 0xa5, 0x94, 0x16, 0x01, 0x8d, 0xe9, 0x9f, 0x40, 0xdd, 0x46, 0xdb, - 0x14, 0x10, 0x97, 0xd5, 0x7a, 0x77, 0xb2, 0x5e, 0xd9, 0xde, 0xb1, 0x76, 0xc6, 0x96, 0x53, 0x21, - 0x81, 0x90, 0x90, 0x38, 0x02, 0xe2, 0xc4, 0x81, 0x1b, 0x12, 0x67, 0x3e, 0x45, 0x8f, 0x15, 0x27, - 0xc4, 0xa1, 0x42, 0xed, 0x27, 0x80, 0x4f, 0x80, 0x76, 0x66, 0x76, 0xeb, 0x5d, 0xff, 0x8d, 0x2b, - 0xc4, 0xc5, 0xde, 0x79, 0xf3, 0x7e, 0x6f, 0xde, 0xbf, 0x79, 0xef, 0xed, 0xc2, 0xd6, 0x63, 0x44, - 0x75, 0xa3, 0xa6, 0xdb, 0x4e, 0x91, 0x3d, 0x61, 0x17, 0x15, 0x0d, 0x17, 0x13, 0xc2, 0x69, 0xb4, - 0x5b, 0x68, 0xb9, 0x98, 0x62, 0x79, 0x3d, 0xe0, 0x2b, 0xf8, 0x7c, 0x85, 0x97, 0x7c, 0xca, 0xb2, - 0x85, 0x2d, 0xcc, 0x38, 0x8b, 0xde, 0x13, 0x07, 0x29, 0x6f, 0x0e, 0x10, 0xde, 0xaa, 0x5b, 0x45, - 0x46, 0x22, 0xe2, 0x4f, 0xf0, 0x6e, 0x0d, 0xe3, 0xc5, 0xb6, 0xc3, 0x7e, 0xc6, 0xc8, 0x6c, 0xb9, - 0x18, 0x1f, 0x12, 0xf1, 0x27, 0x78, 0xaf, 0x8e, 0x36, 0xce, 0xd5, 0x29, 0xd2, 0x1a, 0x76, 0xd3, - 0xa6, 0xc8, 0xd5, 0x0e, 0x1b, 0xba, 0x25, 0x70, 0xf9, 0x1f, 0x24, 0x90, 0x2b, 0xc4, 0xaa, 0xd8, - 0x96, 0xc7, 0x72, 0x40, 0xc8, 0xed, 0xb6, 0x63, 0x12, 0x39, 0x0b, 0xf3, 0x86, 0x8b, 0x74, 0x8a, - 0xdd, 0xac, 0xb4, 0x29, 0x6d, 0x27, 0x55, 0x7f, 0x29, 0xaf, 0x42, 0x82, 0x89, 0xd4, 0x6c, 0x33, - 0x7b, 0x62, 0x53, 0xda, 0x9e, 0x55, 0xe7, 0xd9, 0x7a, 0xcf, 0x94, 0xef, 0x40, 0x5c, 0x6f, 0xe2, - 0xb6, 0x43, 0xb3, 0xb3, 0x1e, 0x66, 0xb7, 0xf8, 0xe4, 0xd9, 0xc6, 0xcc, 0x9f, 0xcf, 0x36, 0xde, - 0xb0, 0x6c, 0x5a, 0x6b, 0x57, 0x0b, 0x06, 0x6e, 0x16, 0x0d, 0x4c, 0x9a, 0x98, 0x88, 0xbf, 0x8b, - 0xc4, 0xac, 0x17, 0xe9, 0x51, 0x0b, 0x91, 0xc2, 0x23, 0xdb, 0xa1, 0xaa, 0x80, 0xe7, 0xcf, 0x80, - 0xd2, 0xaf, 0x93, 0x8a, 0x48, 0x0b, 0x3b, 0x04, 0xe5, 0xef, 0xc3, 0x6b, 0x15, 0x62, 0x3d, 0x6a, - 0x99, 0x7c, 0xf3, 0xa6, 0x69, 0xba, 0x88, 0x8c, 0x52, 0x79, 0x1d, 0x80, 0x12, 0xa2, 0xb5, 0xda, - 0xd5, 0x3a, 0x3a, 0x62, 0x4a, 0x27, 0xd5, 0x24, 0x25, 0x64, 0x9f, 0x11, 0xf2, 0xeb, 0xb0, 0x36, - 0x40, 0x5e, 0x70, 0xdc, 0xcf, 0x27, 0x60, 0xb9, 0x42, 0xac, 0x9b, 0xa6, 0xb9, 0xe7, 0x54, 0x71, - 0xdb, 0x31, 0x0f, 0x5c, 0xdd, 0xa8, 0x23, 0x77, 0x3a, 0x1f, 0x9d, 0x86, 0x79, 0xda, 0xd5, 0x6a, - 0x3a, 0xa9, 0x71, 0x27, 0xa9, 0x71, 0xda, 0xbd, 0xab, 0x93, 0x9a, 0xbc, 0x0b, 0x49, 0x2f, 0xf4, - 0x9a, 0xe7, 0x8e, 0x6c, 0x6c, 0x53, 0xda, 0xce, 0x94, 0xce, 0x17, 0x06, 0x64, 0x62, 0xab, 0x6e, - 0x15, 0x58, 0x8e, 0x94, 0xb1, 0xed, 0x1c, 0x1c, 0xb5, 0x90, 0x9a, 0x30, 0xc4, 0x93, 0x7c, 0x1d, - 0xe6, 0x58, 0x52, 0x64, 0xe7, 0x36, 0xa5, 0xed, 0x54, 0xe9, 0xf5, 0x61, 0x78, 0x91, 0x39, 0xfb, - 0xde, 0x9f, 0xca, 0x21, 0x9e, 0x93, 0xaa, 0x0d, 0x6c, 0xd4, 0xb9, 0x6e, 0x71, 0xee, 0x24, 0x46, - 0x61, 0xea, 0xad, 0x42, 0x82, 0x76, 0x35, 0xdb, 0x31, 0x51, 0x37, 0x3b, 0xcf, 0x4d, 0xa2, 0xdd, - 0x3d, 0x6f, 0x99, 0xcf, 0xc1, 0x99, 0x41, 0xfe, 0x09, 0x1c, 0xf8, 0xbb, 0x04, 0x27, 0x2b, 0xc4, - 0xfa, 0xb4, 0x66, 0x53, 0xd4, 0xb0, 0x09, 0xbd, 0xa5, 0x96, 0x4b, 0x3b, 0x23, 0xbc, 0x77, 0x0e, - 0xd2, 0xc8, 0x35, 0x4a, 0x3b, 0x9a, 0xce, 0x23, 0x21, 0x22, 0xb6, 0xc0, 0x88, 0x7e, 0xb4, 0x7b, - 0x5d, 0x3c, 0x1b, 0x76, 0xb1, 0x0c, 0x31, 0x47, 0x6f, 0x72, 0x27, 0x26, 0x55, 0xf6, 0x2c, 0xaf, - 0x40, 0x9c, 0x1c, 0x35, 0xab, 0xb8, 0xc1, 0x5c, 0x93, 0x54, 0xc5, 0x4a, 0x56, 0x20, 0x61, 0x22, - 0xc3, 0x6e, 0xea, 0x0d, 0xc2, 0x6c, 0x4e, 0xab, 0xc1, 0x5a, 0x5e, 0x83, 0xa4, 0xa5, 0x13, 0x7e, - 0x6b, 0x84, 0xcd, 0x09, 0x4b, 0x27, 0xf7, 0xbc, 0x75, 0x5e, 0x83, 0xd5, 0x3e, 0x9b, 0x7c, 0x8b, - 0x3d, 0x0b, 0x1e, 0x87, 0x2c, 0xe0, 0x16, 0x2e, 0x3c, 0xee, 0xb5, 0x60, 0x1d, 0xc0, 0x30, 0x02, - 0x9f, 0x8a, 0xac, 0xf4, 0x28, 0xdc, 0xab, 0x7f, 0x4b, 0x70, 0x8a, 0xbb, 0xf5, 0x41, 0x9b, 0xbe, - 0x7a, 0xde, 0x2d, 0xc3, 0x9c, 0x83, 0x1d, 0x03, 0x31, 0x67, 0xc5, 0x54, 0xbe, 0xe8, 0xcd, 0xc6, - 0x58, 0x28, 0x1b, 0xff, 0x9f, 0x4c, 0x7a, 0x0f, 0xd6, 0x07, 0x9a, 0x1c, 0x38, 0x76, 0x1d, 0xc0, - 0x26, 0x9a, 0x8b, 0x9a, 0xb8, 0x83, 0x4c, 0x66, 0x7d, 0x42, 0x4d, 0xda, 0x44, 0xe5, 0x84, 0x3c, - 0x82, 0x6c, 0x85, 0x58, 0x7c, 0xf5, 0xdf, 0x79, 0x2d, 0x9f, 0x87, 0xcd, 0x61, 0xc7, 0x04, 0x49, - 0xff, 0xab, 0x04, 0x8b, 0x15, 0x62, 0x7d, 0x82, 0x29, 0xba, 0xa3, 0x93, 0x7d, 0xd7, 0x36, 0xd0, - 0xd4, 0x2a, 0xb4, 0x3c, 0xb4, 0xaf, 0x02, 0x5b, 0xc8, 0x67, 0x61, 0xa1, 0xe5, 0xda, 0xd8, 0xb5, - 0xe9, 0x91, 0x76, 0x88, 0x10, 0xf3, 0x72, 0x4c, 0x4d, 0xf9, 0xb4, 0xdb, 0x88, 0xb1, 0xf0, 0x30, - 0x38, 0xed, 0x66, 0x15, 0xb9, 0x2c, 0xc0, 0x31, 0x35, 0xc5, 0x68, 0xf7, 0x19, 0xe9, 0xa3, 0x58, - 0x62, 0x6e, 0x29, 0x9e, 0x5f, 0x85, 0xd3, 0x11, 0x4d, 0x03, 0x2b, 0x7e, 0x89, 0x07, 0x56, 0xf8, - 0x86, 0x8e, 0xb0, 0x62, 0x0d, 0x58, 0xfe, 0xf2, 0xb8, 0xf3, 0x84, 0x4e, 0x78, 0x04, 0x16, 0xf6, - 0x2b, 0xb0, 0x82, 0xab, 0x04, 0xb9, 0x1d, 0x64, 0x6a, 0x58, 0xc8, 0xea, 0xad, 0x83, 0xcb, 0xfe, - 0xae, 0x7f, 0x10, 0x43, 0x95, 0x21, 0xd7, 0x8f, 0x12, 0xd9, 0x85, 0x6c, 0xab, 0x46, 0x85, 0x59, - 0x6b, 0x51, 0xf4, 0x2e, 0xcb, 0x37, 0xc6, 0x22, 0xdf, 0x00, 0xa5, 0x5f, 0x88, 0x77, 0xb5, 0xdb, - 0x04, 0x99, 0x59, 0x60, 0x02, 0x4e, 0x47, 0x05, 0xdc, 0xd1, 0xc9, 0x23, 0x82, 0x4c, 0xf9, 0x2b, - 0x09, 0xce, 0xf7, 0xa3, 0xd1, 0xe1, 0x21, 0x32, 0xa8, 0xdd, 0x41, 0x4c, 0x0e, 0x0f, 0x50, 0x8a, - 0x35, 0xbd, 0x82, 0x68, 0x7a, 0x5b, 0x13, 0x34, 0xbd, 0x3d, 0x87, 0xaa, 0x67, 0xa3, 0x07, 0xdf, - 0xf2, 0x45, 0x07, 0x79, 0xb3, 0x3f, 0x5e, 0x03, 0x5e, 0xa4, 0x16, 0x98, 0x29, 0x23, 0x25, 0xb2, - 0xea, 0x25, 0x63, 0xc8, 0x74, 0xf4, 0x46, 0x1b, 0x69, 0x2e, 0x32, 0x90, 0xed, 0xdd, 0x25, 0x56, - 0x16, 0x77, 0xef, 0x1e, 0xb3, 0x63, 0xff, 0xf3, 0x6c, 0xe3, 0xd4, 0x91, 0xde, 0x6c, 0x5c, 0xcf, - 0x87, 0xc5, 0xe5, 0xd5, 0x34, 0x23, 0xa8, 0x62, 0x2d, 0x7f, 0x08, 0x71, 0x42, 0x75, 0xda, 0xe6, - 0x55, 0x36, 0x53, 0xba, 0x30, 0xb4, 0xb5, 0xf1, 0x41, 0x49, 0x00, 0x1f, 0x32, 0x8c, 0x2a, 0xb0, - 0xf2, 0x79, 0xc8, 0x04, 0xf6, 0x33, 0x46, 0x51, 0x40, 0xd2, 0x3e, 0xb5, 0xec, 0x11, 0xe5, 0x0b, - 0x20, 0x07, 0x6c, 0x5e, 0xe3, 0xe7, 0x57, 0x38, 0xc1, 0x9c, 0xb3, 0xe4, 0xef, 0x1c, 0x10, 0x72, - 0x9f, 0xd5, 0xc0, 0x50, 0xe3, 0x4d, 0x4e, 0xd5, 0x78, 0x7b, 0xae, 0x90, 0xef, 0xf3, 0xe0, 0x0a, - 0xfd, 0x14, 0x83, 0x8c, 0xd8, 0x13, 0xfd, 0x71, 0xc4, 0x0d, 0xf2, 0xda, 0x14, 0x72, 0x4c, 0xe4, - 0x8a, 0xeb, 0x23, 0x56, 0xf2, 0x16, 0x2c, 0xf2, 0x27, 0x2d, 0xd2, 0xf4, 0xd2, 0x9c, 0x5c, 0x16, - 0xc5, 0x42, 0x81, 0x84, 0x08, 0x81, 0x2b, 0x0a, 0x7a, 0xb0, 0xf6, 0x9c, 0xe7, 0x3f, 0x0b, 0xe7, - 0xcd, 0x71, 0x11, 0x3e, 0x95, 0x3b, 0xef, 0xe5, 0x10, 0x17, 0x7f, 0xa5, 0x21, 0xce, 0xb3, 0xb2, - 0x89, 0x08, 0xd1, 0x2d, 0xee, 0xfa, 0xa4, 0xea, 0x2f, 0xbd, 0xca, 0x64, 0x3b, 0x3d, 0x05, 0x20, - 0xc9, 0xb6, 0x53, 0x82, 0xc6, 0xee, 0xfd, 0x0e, 0x2c, 0xfb, 0x2c, 0xa1, 0xdb, 0xce, 0x2f, 0xab, - 0x2c, 0xf6, 0x7a, 0x2f, 0x79, 0xa8, 0x5b, 0xa7, 0x18, 0x5b, 0xd0, 0xad, 0xc3, 0x31, 0x5e, 0x98, - 0x6e, 0xb8, 0x5a, 0x83, 0x24, 0xed, 0x6a, 0xd8, 0xb5, 0x2d, 0xdb, 0xc9, 0xa6, 0xb9, 0x73, 0x69, - 0xf7, 0x01, 0x5b, 0x7b, 0x55, 0x5a, 0x27, 0x04, 0xd1, 0x6c, 0x86, 0x6d, 0xf0, 0x85, 0xbc, 0x01, - 0x29, 0xd4, 0x41, 0x0e, 0x15, 0xdd, 0x6e, 0x91, 0x69, 0x05, 0x8c, 0xc4, 0x1b, 0x5e, 0x16, 0x56, - 0xc2, 0xb9, 0x11, 0xa4, 0xcd, 0x3d, 0x36, 0x33, 0xdd, 0xac, 0x62, 0x97, 0x3e, 0xa4, 0x6d, 0xa3, - 0x5e, 0x2e, 0x1f, 0x7c, 0x36, 0x7a, 0xc4, 0x1d, 0x35, 0x4c, 0xac, 0xb1, 0x69, 0x25, 0x2c, 0x2d, - 0x38, 0xaa, 0xc3, 0xe6, 0x5b, 0x15, 0x1d, 0xb6, 0x1d, 0x93, 0xb1, 0x20, 0xf3, 0x95, 0x4e, 0xe3, - 0x99, 0xe6, 0x49, 0x0b, 0xe6, 0x1f, 0x5e, 0xe2, 0xd3, 0x9c, 0x2a, 0x06, 0x20, 0x31, 0x37, 0xf6, - 0x9d, 0xfb, 0xf2, 0xe6, 0x48, 0x4c, 0x6b, 0x3e, 0x98, 0xab, 0x3a, 0x45, 0xf7, 0xf8, 0xfb, 0xcb, - 0x6d, 0xef, 0xf5, 0x65, 0x84, 0x76, 0x06, 0xc8, 0xfd, 0xaf, 0x3b, 0x4c, 0xcb, 0x54, 0xa9, 0x58, - 0x18, 0xf9, 0x72, 0x57, 0x88, 0x1e, 0xb3, 0x1b, 0xf3, 0xd2, 0x5f, 0x5d, 0x72, 0x23, 0xf4, 0xfc, - 0x39, 0x38, 0x3b, 0x54, 0x37, 0xdf, 0x82, 0xd2, 0x6f, 0x0b, 0x30, 0x5b, 0x21, 0x96, 0xfc, 0xad, - 0x04, 0xf2, 0x80, 0x41, 0xee, 0xca, 0x18, 0x65, 0x06, 0xce, 0x42, 0xca, 0xbb, 0xd3, 0xa0, 0x82, - 0x09, 0xea, 0x1b, 0x09, 0x4e, 0xf6, 0xbf, 0xca, 0x5c, 0x9e, 0x48, 0x66, 0x18, 0xa4, 0xdc, 0x98, - 0x02, 0x14, 0xe8, 0xf1, 0xbd, 0x04, 0xa7, 0x06, 0x0f, 0x6a, 0x6f, 0x8f, 0x17, 0x3b, 0x10, 0xa8, - 0xbc, 0x3f, 0x25, 0x30, 0xd0, 0xa9, 0x03, 0x0b, 0xa1, 0x79, 0xad, 0x30, 0x5e, 0x60, 0x2f, 0xbf, - 0x72, 0xf5, 0x78, 0xfc, 0xd1, 0x73, 0x83, 0x09, 0x6b, 0xc2, 0x73, 0x7d, 0xfe, 0x49, 0xcf, 0x8d, - 0xb6, 0x26, 0x99, 0x40, 0xaa, 0xb7, 0x2d, 0x5d, 0x9c, 0x4c, 0x8c, 0x60, 0x57, 0xde, 0x3a, 0x16, - 0x7b, 0x70, 0xe8, 0x17, 0x90, 0x89, 0xbc, 0x09, 0xee, 0x8c, 0x17, 0x14, 0x46, 0x28, 0xd7, 0x8e, - 0x8b, 0x08, 0x4e, 0xff, 0x5a, 0x82, 0xa5, 0xbe, 0x2f, 0x07, 0xa5, 0xf1, 0xe2, 0xa2, 0x18, 0xe5, - 0xfa, 0xf1, 0x31, 0x81, 0x12, 0x5f, 0xc2, 0x62, 0xf4, 0x7b, 0xcb, 0xa5, 0xf1, 0xe2, 0x22, 0x10, - 0xe5, 0x9d, 0x63, 0x43, 0x7a, 0x63, 0x10, 0xe9, 0x2c, 0x13, 0xc4, 0x20, 0x8c, 0x98, 0x24, 0x06, - 0x83, 0xfb, 0x0d, 0x2b, 0x41, 0xfd, 0xdd, 0xe6, 0xf2, 0x24, 0xb7, 0x37, 0x02, 0x9a, 0xa4, 0x04, - 0x0d, 0xed, 0x2f, 0xf2, 0x8f, 0x12, 0xac, 0x0c, 0x69, 0x2e, 0xd7, 0x26, 0x8d, 0x6e, 0x14, 0xa9, - 0x7c, 0x30, 0x2d, 0xd2, 0x57, 0x6b, 0xf7, 0xe3, 0x27, 0xcf, 0x73, 0xd2, 0xd3, 0xe7, 0x39, 0xe9, - 0xaf, 0xe7, 0x39, 0xe9, 0xbb, 0x17, 0xb9, 0x99, 0xa7, 0x2f, 0x72, 0x33, 0x7f, 0xbc, 0xc8, 0xcd, - 0x7c, 0x7e, 0xa9, 0x67, 0x04, 0xf3, 0x64, 0x5f, 0x8c, 0x7c, 0xef, 0xeb, 0x86, 0x3e, 0x67, 0x7a, - 0x13, 0x59, 0x35, 0xce, 0xbe, 0xf2, 0x5d, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0xda, 0xb5, 0x92, - 0x0a, 0xfc, 0x14, 0x00, 0x00, + // 1638 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0x5f, 0x6f, 0xdb, 0x54, + 0x14, 0xaf, 0xd7, 0x34, 0x4d, 0x4e, 0x9a, 0xb6, 0xf3, 0xba, 0x2e, 0x75, 0xd7, 0xb4, 0xcb, 0x58, + 0xa9, 0xd0, 0x96, 0x74, 0xd9, 0x18, 0xa3, 0x43, 0xc0, 0x9a, 0xfd, 0x2b, 0x2c, 0x5b, 0xe5, 0x75, + 0x80, 0x78, 0xb1, 0x1c, 0xfb, 0xd6, 0xb5, 0x9a, 0xf8, 0x46, 0xbe, 0x37, 0x5d, 0x32, 0x21, 0x81, + 0x90, 0x90, 0x78, 0x04, 0xc4, 0xd3, 0x1e, 0x78, 0x43, 0xe2, 0xa3, 0xec, 0x8d, 0x89, 0x27, 0xc4, + 0xc3, 0x84, 0xb6, 0x2f, 0x00, 0x7c, 0x02, 0xe4, 0x7b, 0xaf, 0xdd, 0xd8, 0xf9, 0xdb, 0x54, 0x88, + 0x97, 0xc4, 0xf7, 0xf8, 0xfc, 0xce, 0x3d, 0xff, 0xee, 0x39, 0xe7, 0x1a, 0x56, 0x9f, 0x22, 0xaa, + 0x1b, 0x7b, 0xba, 0xed, 0x14, 0xd8, 0x13, 0x76, 0x51, 0xc1, 0x70, 0x31, 0x21, 0x9c, 0x46, 0x9b, + 0xf9, 0xba, 0x8b, 0x29, 0x96, 0x97, 0x02, 0xbe, 0xbc, 0xcf, 0x97, 0x3f, 0xe4, 0x53, 0xe6, 0x2c, + 0x6c, 0x61, 0xc6, 0x59, 0xf0, 0x9e, 0x38, 0x48, 0x79, 0xab, 0x8b, 0xf0, 0xfa, 0xbe, 0x55, 0x60, + 0x24, 0x22, 0xfe, 0x04, 0xef, 0x6a, 0x2f, 0x5e, 0x6c, 0x3b, 0xec, 0x67, 0x80, 0xcc, 0xba, 0x8b, + 0xf1, 0x2e, 0x11, 0x7f, 0x82, 0xf7, 0x5a, 0x7f, 0xe3, 0x5c, 0x9d, 0x22, 0xad, 0x6a, 0xd7, 0x6c, + 0x8a, 0x5c, 0x6d, 0xb7, 0xaa, 0x5b, 0x02, 0x97, 0xfb, 0x41, 0x02, 0xb9, 0x4c, 0xac, 0xb2, 0x6d, + 0x79, 0x2c, 0x3b, 0x84, 0xdc, 0x69, 0x38, 0x26, 0x91, 0x33, 0x30, 0x69, 0xb8, 0x48, 0xa7, 0xd8, + 0xcd, 0x48, 0x2b, 0xd2, 0x5a, 0x52, 0xf5, 0x97, 0xf2, 0x02, 0x24, 0x98, 0x48, 0xcd, 0x36, 0x33, + 0x27, 0x56, 0xa4, 0xb5, 0x71, 0x75, 0x92, 0xad, 0xb7, 0x4c, 0xf9, 0x2e, 0xc4, 0xf5, 0x1a, 0x6e, + 0x38, 0x34, 0x33, 0xee, 0x61, 0x36, 0x0b, 0xcf, 0x5f, 0x2e, 0x8f, 0xfd, 0xf1, 0x72, 0xf9, 0x4d, + 0xcb, 0xa6, 0x7b, 0x8d, 0x4a, 0xde, 0xc0, 0xb5, 0x82, 0x81, 0x49, 0x0d, 0x13, 0xf1, 0x77, 0x89, + 0x98, 0xfb, 0x05, 0xda, 0xaa, 0x23, 0x92, 0x7f, 0x6c, 0x3b, 0x54, 0x15, 0xf0, 0xdc, 0x59, 0x50, + 0x3a, 0x75, 0x52, 0x11, 0xa9, 0x63, 0x87, 0xa0, 0xdc, 0x03, 0x38, 0x55, 0x26, 0xd6, 0xe3, 0xba, + 0xc9, 0x5f, 0xde, 0x34, 0x4d, 0x17, 0x91, 0x7e, 0x2a, 0x2f, 0x01, 0x50, 0x42, 0xb4, 0x7a, 0xa3, + 0xb2, 0x8f, 0x5a, 0x4c, 0xe9, 0xa4, 0x9a, 0xa4, 0x84, 0x6c, 0x33, 0x42, 0x6e, 0x09, 0x16, 0xbb, + 0xc8, 0x0b, 0xb6, 0xfb, 0xe9, 0x04, 0xcc, 0x95, 0x89, 0x75, 0xd3, 0x34, 0xb7, 0x9c, 0x0a, 0x6e, + 0x38, 0xe6, 0x8e, 0xab, 0x1b, 0xfb, 0xc8, 0x1d, 0xcd, 0x47, 0x67, 0x60, 0x92, 0x36, 0xb5, 0x3d, + 0x9d, 0xec, 0x71, 0x27, 0xa9, 0x71, 0xda, 0xbc, 0xa7, 0x93, 0x3d, 0x79, 0x13, 0x92, 0x5e, 0xe8, + 0x35, 0xcf, 0x1d, 0x99, 0xd8, 0x8a, 0xb4, 0x36, 0x5d, 0xbc, 0x90, 0xef, 0x92, 0x89, 0xf5, 0x7d, + 0x2b, 0xcf, 0x72, 0xa4, 0x84, 0x6d, 0x67, 0xa7, 0x55, 0x47, 0x6a, 0xc2, 0x10, 0x4f, 0xf2, 0x06, + 0x4c, 0xb0, 0xa4, 0xc8, 0x4c, 0xac, 0x48, 0x6b, 0xa9, 0xe2, 0x1b, 0xbd, 0xf0, 0x22, 0x73, 0xb6, + 0xbd, 0x3f, 0x95, 0x43, 0x3c, 0x27, 0x55, 0xaa, 0xd8, 0xd8, 0xe7, 0xba, 0xc5, 0xb9, 0x93, 0x18, + 0x85, 0xa9, 0xb7, 0x00, 0x09, 0xda, 0xd4, 0x6c, 0xc7, 0x44, 0xcd, 0xcc, 0x24, 0x37, 0x89, 0x36, + 0xb7, 0xbc, 0x65, 0x2e, 0x0b, 0x67, 0xbb, 0xf9, 0x27, 0x70, 0xe0, 0x6f, 0x12, 0x9c, 0x2c, 0x13, + 0xeb, 0xd3, 0x3d, 0x9b, 0xa2, 0xaa, 0x4d, 0xe8, 0x6d, 0xb5, 0x54, 0x5c, 0xef, 0xe3, 0xbd, 0xf3, + 0x90, 0x46, 0xae, 0x51, 0x5c, 0xd7, 0x74, 0x1e, 0x09, 0x11, 0xb1, 0x29, 0x46, 0xf4, 0xa3, 0xdd, + 0xee, 0xe2, 0xf1, 0xb0, 0x8b, 0x65, 0x88, 0x39, 0x7a, 0x8d, 0x3b, 0x31, 0xa9, 0xb2, 0x67, 0x79, + 0x1e, 0xe2, 0xa4, 0x55, 0xab, 0xe0, 0x2a, 0x73, 0x4d, 0x52, 0x15, 0x2b, 0x59, 0x81, 0x84, 0x89, + 0x0c, 0xbb, 0xa6, 0x57, 0x09, 0xb3, 0x39, 0xad, 0x06, 0x6b, 0x79, 0x11, 0x92, 0x96, 0x4e, 0xf8, + 0xa9, 0x11, 0x36, 0x27, 0x2c, 0x9d, 0xdc, 0xf7, 0xd6, 0x39, 0x0d, 0x16, 0x3a, 0x6c, 0xf2, 0x2d, + 0xf6, 0x2c, 0x78, 0x1a, 0xb2, 0x80, 0x5b, 0x38, 0xf5, 0xb4, 0xdd, 0x82, 0x25, 0x00, 0xc3, 0x08, + 0x7c, 0x2a, 0xb2, 0xd2, 0xa3, 0x70, 0xaf, 0xfe, 0x2d, 0xc1, 0x69, 0xee, 0xd6, 0x87, 0x0d, 0x7a, + 0xfc, 0xbc, 0x9b, 0x83, 0x09, 0x07, 0x3b, 0x06, 0x62, 0xce, 0x8a, 0xa9, 0x7c, 0xd1, 0x9e, 0x8d, + 0xb1, 0x50, 0x36, 0xfe, 0x3f, 0x99, 0xf4, 0x3e, 0x2c, 0x75, 0x35, 0x39, 0x70, 0xec, 0x12, 0x80, + 0x4d, 0x34, 0x17, 0xd5, 0xf0, 0x01, 0x32, 0x99, 0xf5, 0x09, 0x35, 0x69, 0x13, 0x95, 0x13, 0x72, + 0x08, 0x32, 0x65, 0x62, 0xf1, 0xd5, 0x7f, 0xe7, 0xb5, 0x5c, 0x0e, 0x56, 0x7a, 0x6d, 0x13, 0x24, + 0xfd, 0x2f, 0x12, 0xcc, 0x94, 0x89, 0xf5, 0x09, 0xa6, 0xe8, 0xae, 0x4e, 0xb6, 0x5d, 0xdb, 0x40, + 0x23, 0xab, 0x50, 0xf7, 0xd0, 0xbe, 0x0a, 0x6c, 0x21, 0x9f, 0x83, 0xa9, 0xba, 0x6b, 0x63, 0xd7, + 0xa6, 0x2d, 0x6d, 0x17, 0x21, 0xe6, 0xe5, 0x98, 0x9a, 0xf2, 0x69, 0x77, 0x10, 0x63, 0xe1, 0x61, + 0x70, 0x1a, 0xb5, 0x0a, 0x72, 0x59, 0x80, 0x63, 0x6a, 0x8a, 0xd1, 0x1e, 0x30, 0xd2, 0x47, 0xb1, + 0xc4, 0xc4, 0x6c, 0x3c, 0xb7, 0x00, 0x67, 0x22, 0x9a, 0x06, 0x56, 0xfc, 0x1c, 0x0f, 0xac, 0xf0, + 0x0d, 0xed, 0x63, 0xc5, 0x22, 0xb0, 0xfc, 0xe5, 0x71, 0xe7, 0x09, 0x9d, 0xf0, 0x08, 0x2c, 0xec, + 0x57, 0x61, 0x1e, 0x57, 0x08, 0x72, 0x0f, 0x90, 0xa9, 0x61, 0x21, 0xab, 0xbd, 0x0e, 0xce, 0xf9, + 0x6f, 0xfd, 0x8d, 0x18, 0xaa, 0x04, 0xd9, 0x4e, 0x94, 0xc8, 0x2e, 0x64, 0x5b, 0x7b, 0x54, 0x98, + 0xb5, 0x18, 0x45, 0x6f, 0xb2, 0x7c, 0x63, 0x2c, 0xf2, 0x0d, 0x50, 0x3a, 0x85, 0x78, 0x47, 0xbb, + 0x41, 0x90, 0x99, 0x01, 0x26, 0xe0, 0x4c, 0x54, 0xc0, 0x5d, 0x9d, 0x3c, 0x26, 0xc8, 0x94, 0xbf, + 0x92, 0xe0, 0x42, 0x27, 0x1a, 0xed, 0xee, 0x22, 0x83, 0xda, 0x07, 0x88, 0xc9, 0xe1, 0x01, 0x4a, + 0xb1, 0xa6, 0x97, 0x17, 0x4d, 0x6f, 0x75, 0x88, 0xa6, 0xb7, 0xe5, 0x50, 0xf5, 0x5c, 0x74, 0xe3, + 0xdb, 0xbe, 0xe8, 0x20, 0x6f, 0xb6, 0x07, 0x6b, 0xc0, 0x8b, 0xd4, 0x14, 0x33, 0xa5, 0xaf, 0x44, + 0x56, 0xbd, 0x64, 0x0c, 0xd3, 0x07, 0x7a, 0xb5, 0x81, 0x34, 0x17, 0x19, 0xc8, 0xf6, 0xce, 0x12, + 0x2b, 0x8b, 0x9b, 0xf7, 0x8e, 0xd8, 0xb1, 0xff, 0x79, 0xb9, 0x7c, 0xba, 0xa5, 0xd7, 0xaa, 0x1b, + 0xb9, 0xb0, 0xb8, 0x9c, 0x9a, 0x66, 0x04, 0x55, 0xac, 0xe5, 0x5b, 0x10, 0x27, 0x54, 0xa7, 0x0d, + 0x5e, 0x65, 0xa7, 0x8b, 0x17, 0x7b, 0xb6, 0x36, 0x3e, 0x28, 0x09, 0xe0, 0x23, 0x86, 0x51, 0x05, + 0x56, 0xbe, 0x00, 0xd3, 0x81, 0xfd, 0x8c, 0x51, 0x14, 0x90, 0xb4, 0x4f, 0x2d, 0x79, 0x44, 0xf9, + 0x22, 0xc8, 0x01, 0x9b, 0xd7, 0xf8, 0xf9, 0x11, 0x4e, 0x30, 0xe7, 0xcc, 0xfa, 0x6f, 0x76, 0x08, + 0x79, 0xc0, 0x6a, 0x60, 0xa8, 0xf1, 0x26, 0x47, 0x6a, 0xbc, 0x6d, 0x47, 0xc8, 0xf7, 0x79, 0x70, + 0x84, 0x9e, 0xc5, 0x60, 0x5a, 0xbc, 0x13, 0xfd, 0xb1, 0xcf, 0x09, 0xf2, 0xda, 0x14, 0x72, 0x4c, + 0xe4, 0x8a, 0xe3, 0x23, 0x56, 0xf2, 0x2a, 0xcc, 0xf0, 0x27, 0x2d, 0xd2, 0xf4, 0xd2, 0x9c, 0x5c, + 0x12, 0xc5, 0x42, 0x81, 0x84, 0x08, 0x81, 0x2b, 0x0a, 0x7a, 0xb0, 0xf6, 0x9c, 0xe7, 0x3f, 0x0b, + 0xe7, 0x4d, 0x70, 0x11, 0x3e, 0x95, 0x3b, 0xef, 0x70, 0x88, 0x8b, 0x1f, 0x6b, 0x88, 0xf3, 0xac, + 0xac, 0x21, 0x42, 0x74, 0x8b, 0xbb, 0x3e, 0xa9, 0xfa, 0x4b, 0xaf, 0x32, 0xd9, 0x4e, 0x5b, 0x01, + 0x48, 0xb2, 0xd7, 0x29, 0x41, 0x63, 0xe7, 0x7e, 0x1d, 0xe6, 0x7c, 0x96, 0xd0, 0x69, 0xe7, 0x87, + 0x55, 0x16, 0xef, 0xda, 0x0f, 0x79, 0xa8, 0x5b, 0xa7, 0x18, 0x5b, 0xd0, 0xad, 0xc3, 0x31, 0x9e, + 0x1a, 0x6d, 0xb8, 0x5a, 0x84, 0x24, 0x6d, 0x6a, 0xd8, 0xb5, 0x2d, 0xdb, 0xc9, 0xa4, 0xb9, 0x73, + 0x69, 0xf3, 0x21, 0x5b, 0x7b, 0x55, 0x5a, 0x27, 0x04, 0xd1, 0xcc, 0x34, 0x7b, 0xc1, 0x17, 0xf2, + 0x32, 0xa4, 0xd0, 0x01, 0x72, 0xa8, 0xe8, 0x76, 0x33, 0x4c, 0x2b, 0x60, 0x24, 0xde, 0xf0, 0x32, + 0x30, 0x1f, 0xce, 0x8d, 0x20, 0x6d, 0xee, 0xb3, 0x99, 0xe9, 0x66, 0x05, 0xbb, 0xf4, 0x11, 0x6d, + 0x18, 0xfb, 0xa5, 0xd2, 0xce, 0x67, 0xfd, 0x47, 0xdc, 0x7e, 0xc3, 0xc4, 0x22, 0x9b, 0x56, 0xc2, + 0xd2, 0x82, 0xad, 0x0e, 0xd8, 0x7c, 0xab, 0xa2, 0xdd, 0x86, 0x63, 0x32, 0x16, 0x64, 0x1e, 0x6b, + 0x37, 0x9e, 0x69, 0x9e, 0xb4, 0x60, 0xfe, 0xe1, 0x25, 0x3e, 0xcd, 0xa9, 0x62, 0x00, 0x12, 0x73, + 0x63, 0xc7, 0xbe, 0x87, 0x27, 0x47, 0x62, 0x5a, 0xf3, 0xc1, 0x5c, 0xd5, 0x29, 0xba, 0xcf, 0xef, + 0x2f, 0x77, 0xbc, 0xeb, 0x4b, 0x1f, 0xed, 0x0c, 0x90, 0x3b, 0xaf, 0x3b, 0x4c, 0xcb, 0x54, 0xb1, + 0x90, 0xef, 0x7b, 0xb9, 0xcb, 0x47, 0xb7, 0xd9, 0x8c, 0x79, 0xe9, 0xaf, 0xce, 0xba, 0x11, 0x7a, + 0xee, 0x3c, 0x9c, 0xeb, 0xa9, 0x5b, 0x60, 0xc1, 0x5f, 0x12, 0xbb, 0x5a, 0x88, 0x8b, 0x0c, 0x9b, + 0x11, 0x4b, 0x0d, 0x42, 0xb1, 0xd9, 0x3a, 0xc6, 0x2d, 0x2b, 0x0f, 0xa7, 0x1c, 0xf4, 0x44, 0x33, + 0xb8, 0xa0, 0x88, 0x8b, 0x4f, 0x3a, 0xe8, 0x89, 0xd8, 0xc2, 0x9f, 0x33, 0x3b, 0xc6, 0xe9, 0x58, + 0x97, 0x71, 0xfa, 0xf0, 0xd4, 0x4f, 0x1c, 0xef, 0xea, 0x76, 0x0b, 0xce, 0xf7, 0xb1, 0xb8, 0x7d, + 0x90, 0x6b, 0xcb, 0x20, 0x29, 0x92, 0x41, 0xc5, 0x5f, 0xd3, 0x30, 0x5e, 0x26, 0x96, 0xfc, 0xad, + 0x04, 0x72, 0x97, 0x09, 0xf8, 0xea, 0x80, 0x28, 0x76, 0x1d, 0x22, 0x95, 0xf7, 0x46, 0x41, 0x05, + 0x1a, 0x7f, 0x23, 0xc1, 0xc9, 0xce, 0x3b, 0xe0, 0x95, 0xa1, 0x64, 0x86, 0x41, 0xca, 0x8d, 0x11, + 0x40, 0x81, 0x1e, 0xdf, 0x4b, 0x70, 0xba, 0xfb, 0x84, 0xfb, 0xce, 0x60, 0xb1, 0x5d, 0x81, 0xca, + 0x07, 0x23, 0x02, 0x03, 0x9d, 0x0e, 0x60, 0x2a, 0x34, 0xe8, 0xe6, 0x07, 0x0b, 0x6c, 0xe7, 0x57, + 0xae, 0x1d, 0x8d, 0x3f, 0xba, 0x6f, 0x30, 0x9a, 0x0e, 0xb9, 0xaf, 0xcf, 0x3f, 0xec, 0xbe, 0xd1, + 0x9e, 0x2e, 0x13, 0x48, 0xb5, 0xf7, 0xf3, 0x4b, 0xc3, 0x89, 0x11, 0xec, 0xca, 0xdb, 0x47, 0x62, + 0x0f, 0x36, 0xfd, 0x02, 0xa6, 0x23, 0x57, 0xe8, 0xf5, 0xc1, 0x82, 0xc2, 0x08, 0xe5, 0xfa, 0x51, + 0x11, 0xc1, 0xee, 0x5f, 0x4b, 0x30, 0xdb, 0xf1, 0xc9, 0xa5, 0x38, 0x58, 0x5c, 0x14, 0xa3, 0x6c, + 0x1c, 0x1d, 0x13, 0x28, 0xf1, 0x25, 0xcc, 0x44, 0x3f, 0x54, 0x5d, 0x1e, 0x2c, 0x2e, 0x02, 0x51, + 0xde, 0x3d, 0x32, 0xa4, 0x3d, 0x06, 0x91, 0x96, 0x3c, 0x44, 0x0c, 0xc2, 0x88, 0x61, 0x62, 0xd0, + 0xbd, 0x51, 0xb3, 0x12, 0xd4, 0xd9, 0xa6, 0xaf, 0x0c, 0x73, 0x7a, 0x23, 0xa0, 0x61, 0x4a, 0x50, + 0xcf, 0xc6, 0x2c, 0xff, 0x28, 0xc1, 0x7c, 0x8f, 0xae, 0x7c, 0x7d, 0xd8, 0xe8, 0x46, 0x91, 0xca, + 0x87, 0xa3, 0x22, 0x03, 0xb5, 0x9e, 0x49, 0x90, 0xe9, 0xd9, 0x6a, 0x37, 0x86, 0x0e, 0x7a, 0x07, + 0x56, 0xd9, 0x1c, 0x1d, 0xeb, 0x2b, 0xb7, 0xf9, 0xf1, 0xf3, 0x57, 0x59, 0xe9, 0xc5, 0xab, 0xac, + 0xf4, 0xe7, 0xab, 0xac, 0xf4, 0xdd, 0xeb, 0xec, 0xd8, 0x8b, 0xd7, 0xd9, 0xb1, 0xdf, 0x5f, 0x67, + 0xc7, 0x3e, 0xbf, 0xdc, 0xd6, 0x62, 0x3d, 0xe9, 0x97, 0x22, 0x5f, 0x71, 0x9b, 0xa1, 0x8f, 0xd4, + 0x5e, 0xc7, 0xad, 0xc4, 0xd9, 0xb7, 0xdb, 0x2b, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x89, 0xad, + 0xbd, 0xf8, 0xd2, 0x16, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1588,6 +1708,7 @@ type MsgClient interface { AbortStuckCCTX(ctx context.Context, in *MsgAbortStuckCCTX, opts ...grpc.CallOption) (*MsgAbortStuckCCTXResponse, error) RefundAbortedCCTX(ctx context.Context, in *MsgRefundAbortedCCTX, opts ...grpc.CallOption) (*MsgRefundAbortedCCTXResponse, error) UpdateRateLimiterFlags(ctx context.Context, in *MsgUpdateRateLimiterFlags, opts ...grpc.CallOption) (*MsgUpdateRateLimiterFlagsResponse, error) + MigrateERC20CustodyFunds(ctx context.Context, in *MsgMigrateERC20CustodyFunds, opts ...grpc.CallOption) (*MsgMigrateERC20CustodyFundsResponse, error) } type msgClient struct { @@ -1706,6 +1827,15 @@ func (c *msgClient) UpdateRateLimiterFlags(ctx context.Context, in *MsgUpdateRat return out, nil } +func (c *msgClient) MigrateERC20CustodyFunds(ctx context.Context, in *MsgMigrateERC20CustodyFunds, opts ...grpc.CallOption) (*MsgMigrateERC20CustodyFundsResponse, error) { + out := new(MsgMigrateERC20CustodyFundsResponse) + err := c.cc.Invoke(ctx, "/zetachain.zetacore.crosschain.Msg/MigrateERC20CustodyFunds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { AddOutboundTracker(context.Context, *MsgAddOutboundTracker) (*MsgAddOutboundTrackerResponse, error) @@ -1720,6 +1850,7 @@ type MsgServer interface { AbortStuckCCTX(context.Context, *MsgAbortStuckCCTX) (*MsgAbortStuckCCTXResponse, error) RefundAbortedCCTX(context.Context, *MsgRefundAbortedCCTX) (*MsgRefundAbortedCCTXResponse, error) UpdateRateLimiterFlags(context.Context, *MsgUpdateRateLimiterFlags) (*MsgUpdateRateLimiterFlagsResponse, error) + MigrateERC20CustodyFunds(context.Context, *MsgMigrateERC20CustodyFunds) (*MsgMigrateERC20CustodyFundsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -1762,6 +1893,9 @@ func (*UnimplementedMsgServer) RefundAbortedCCTX(ctx context.Context, req *MsgRe func (*UnimplementedMsgServer) UpdateRateLimiterFlags(ctx context.Context, req *MsgUpdateRateLimiterFlags) (*MsgUpdateRateLimiterFlagsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateRateLimiterFlags not implemented") } +func (*UnimplementedMsgServer) MigrateERC20CustodyFunds(ctx context.Context, req *MsgMigrateERC20CustodyFunds) (*MsgMigrateERC20CustodyFundsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MigrateERC20CustodyFunds not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -1983,6 +2117,24 @@ func _Msg_UpdateRateLimiterFlags_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _Msg_MigrateERC20CustodyFunds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgMigrateERC20CustodyFunds) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).MigrateERC20CustodyFunds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/zetachain.zetacore.crosschain.Msg/MigrateERC20CustodyFunds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).MigrateERC20CustodyFunds(ctx, req.(*MsgMigrateERC20CustodyFunds)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "zetachain.zetacore.crosschain.Msg", HandlerType: (*MsgServer)(nil), @@ -2035,6 +2187,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateRateLimiterFlags", Handler: _Msg_UpdateRateLimiterFlags_Handler, }, + { + MethodName: "MigrateERC20CustodyFunds", + Handler: _Msg_MigrateERC20CustodyFunds_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "zetachain/zetacore/crosschain/tx.proto", @@ -3052,6 +3208,95 @@ func (m *MsgUpdateRateLimiterFlagsResponse) MarshalToSizedBuffer(dAtA []byte) (i return len(dAtA) - i, nil } +func (m *MsgMigrateERC20CustodyFunds) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMigrateERC20CustodyFunds) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMigrateERC20CustodyFunds) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Amount.Size() + i -= size + if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if len(m.Erc20Address) > 0 { + i -= len(m.Erc20Address) + copy(dAtA[i:], m.Erc20Address) + i = encodeVarintTx(dAtA, i, uint64(len(m.Erc20Address))) + i-- + dAtA[i] = 0x22 + } + if len(m.NewCustodyAddress) > 0 { + i -= len(m.NewCustodyAddress) + copy(dAtA[i:], m.NewCustodyAddress) + i = encodeVarintTx(dAtA, i, uint64(len(m.NewCustodyAddress))) + i-- + dAtA[i] = 0x1a + } + if m.ChainId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.ChainId)) + i-- + dAtA[i] = 0x10 + } + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgMigrateERC20CustodyFundsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMigrateERC20CustodyFundsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMigrateERC20CustodyFundsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CctxIndex) > 0 { + i -= len(m.CctxIndex) + copy(dAtA[i:], m.CctxIndex) + i = encodeVarintTx(dAtA, i, uint64(len(m.CctxIndex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -3519,6 +3764,45 @@ func (m *MsgUpdateRateLimiterFlagsResponse) Size() (n int) { return n } +func (m *MsgMigrateERC20CustodyFunds) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ChainId != 0 { + n += 1 + sovTx(uint64(m.ChainId)) + } + l = len(m.NewCustodyAddress) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Erc20Address) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgMigrateERC20CustodyFundsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CctxIndex) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -6593,6 +6877,287 @@ func (m *MsgUpdateRateLimiterFlagsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgMigrateERC20CustodyFunds) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFunds: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFunds: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + m.ChainId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ChainId |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewCustodyAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewCustodyAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Erc20Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Erc20Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMigrateERC20CustodyFundsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFundsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFundsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxIndex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxIndex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go index 6021b9219e..cf5491366b 100644 --- a/zetaclient/chains/base/observer.go +++ b/zetaclient/chains/base/observer.go @@ -17,6 +17,7 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/db" + "github.com/zeta-chain/zetacore/zetaclient/logs" "github.com/zeta-chain/zetacore/zetaclient/metrics" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "github.com/zeta-chain/zetacore/zetaclient/zetacore" @@ -301,13 +302,13 @@ func (ob *Observer) Logger() *ObserverLogger { // WithLogger attaches a new logger to the observer. func (ob *Observer) WithLogger(logger Logger) *Observer { - chainLogger := logger.Std.With().Int64("chain", ob.chain.ChainId).Logger() + chainLogger := logger.Std.With().Int64(logs.FieldChain, ob.chain.ChainId).Logger() ob.logger = ObserverLogger{ Chain: chainLogger, - Inbound: chainLogger.With().Str("module", "inbound").Logger(), - Outbound: chainLogger.With().Str("module", "outbound").Logger(), - GasPrice: chainLogger.With().Str("module", "gasprice").Logger(), - Headers: chainLogger.With().Str("module", "headers").Logger(), + Inbound: chainLogger.With().Str(logs.FieldModule, logs.ModNameInbound).Logger(), + Outbound: chainLogger.With().Str(logs.FieldModule, logs.ModNameOutbound).Logger(), + GasPrice: chainLogger.With().Str(logs.FieldModule, logs.ModNameGasPrice).Logger(), + Headers: chainLogger.With().Str(logs.FieldModule, logs.ModNameHeaders).Logger(), Compliance: logger.Compliance, } return ob diff --git a/zetaclient/chains/base/signer.go b/zetaclient/chains/base/signer.go index 6618c338de..781288b513 100644 --- a/zetaclient/chains/base/signer.go +++ b/zetaclient/chains/base/signer.go @@ -5,6 +5,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/logs" "github.com/zeta-chain/zetacore/zetaclient/metrics" ) @@ -38,7 +39,10 @@ func NewSigner(chain chains.Chain, tss interfaces.TSSSigner, ts *metrics.Telemet tss: tss, ts: ts, logger: Logger{ - Std: logger.Std.With().Int64("chain", chain.ChainId).Str("module", "signer").Logger(), + Std: logger.Std.With(). + Int64(logs.FieldChain, chain.ChainId). + Str(logs.FieldModule, "signer"). + Logger(), Compliance: logger.Compliance, }, outboundBeingReported: make(map[string]bool), diff --git a/zetaclient/chains/evm/constant.go b/zetaclient/chains/evm/constant.go index b754d57f30..beaeb6143f 100644 --- a/zetaclient/chains/evm/constant.go +++ b/zetaclient/chains/evm/constant.go @@ -3,12 +3,13 @@ package evm import "time" const ( - // ZetaBlockTime is the block time of the Zeta network - ZetaBlockTime = 6500 * time.Millisecond - // OutboundInclusionTimeout is the timeout for waiting for an outbound to be included in a block OutboundInclusionTimeout = 20 * time.Minute + // ReorgProtectBlockCount is confirmations count to protect against reorg + // Short 1~2 block reorgs could happen often on Ethereum due to network congestion or block production race conditions + ReorgProtectBlockCount = 2 + // OutboundTrackerReportTimeout is the timeout for waiting for an outbound tracker report OutboundTrackerReportTimeout = 10 * time.Minute diff --git a/zetaclient/chains/evm/observer/observer.go b/zetaclient/chains/evm/observer/observer.go index 4959b13b9a..b5e7be3f6e 100644 --- a/zetaclient/chains/evm/observer/observer.go +++ b/zetaclient/chains/evm/observer/observer.go @@ -43,9 +43,6 @@ type Observer struct { // evmJSONRPC is the EVM JSON RPC client for the observed chain evmJSONRPC interfaces.EVMJSONRPCClient - // outboundPendingTransactions is the map to index pending transactions by hash - outboundPendingTransactions map[string]*ethtypes.Transaction - // outboundConfirmedReceipts is the map to index confirmed receipts by hash outboundConfirmedReceipts map[string]*ethtypes.Receipt @@ -92,7 +89,6 @@ func NewObserver( Observer: *baseObserver, evmClient: evmClient, evmJSONRPC: ethrpc.NewEthRPC(evmCfg.Endpoint), - outboundPendingTransactions: make(map[string]*ethtypes.Transaction), outboundConfirmedReceipts: make(map[string]*ethtypes.Receipt), outboundConfirmedTransactions: make(map[string]*ethtypes.Transaction), priorityFeeConfig: priorityFeeConfig{}, @@ -230,25 +226,10 @@ func (ob *Observer) WatchRPCStatus(ctx context.Context) error { } } -// SetPendingTx sets the pending transaction in memory -func (ob *Observer) SetPendingTx(nonce uint64, transaction *ethtypes.Transaction) { - ob.Mu().Lock() - defer ob.Mu().Unlock() - ob.outboundPendingTransactions[ob.OutboundID(nonce)] = transaction -} - -// GetPendingTx gets the pending transaction from memory -func (ob *Observer) GetPendingTx(nonce uint64) *ethtypes.Transaction { - ob.Mu().Lock() - defer ob.Mu().Unlock() - return ob.outboundPendingTransactions[ob.OutboundID(nonce)] -} - // SetTxNReceipt sets the receipt and transaction in memory func (ob *Observer) SetTxNReceipt(nonce uint64, receipt *ethtypes.Receipt, transaction *ethtypes.Transaction) { ob.Mu().Lock() defer ob.Mu().Unlock() - delete(ob.outboundPendingTransactions, ob.OutboundID(nonce)) // remove pending transaction, if any ob.outboundConfirmedReceipts[ob.OutboundID(nonce)] = receipt ob.outboundConfirmedTransactions[ob.OutboundID(nonce)] = transaction } diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index 680fb4e3af..598b9b6a44 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -13,25 +13,33 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" + crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/evm" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/compliance" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/logs" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "github.com/zeta-chain/zetacore/zetaclient/zetacore" ) // WatchOutbound watches evm chain for outgoing txs status // TODO(revamp): move ticker function to ticker file -// TODO(revamp): move inner logic to a separate function func (ob *Observer) WatchOutbound(ctx context.Context) error { + // get app context + app, err := zctx.FromContext(ctx) + if err != nil { + return err + } + + // create outbound ticker + chainID := ob.Chain().ChainId ticker, err := clienttypes.NewDynamicTicker( fmt.Sprintf("EVM_WatchOutbound_%d", ob.Chain().ChainId), ob.GetChainParams().OutboundTicker, @@ -41,11 +49,6 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { return err } - app, err := zctx.FromContext(ctx) - if err != nil { - return err - } - ob.Logger().Outbound.Info().Msgf("WatchOutbound started for chain %d", ob.Chain().ChainId) sampledLogger := ob.Logger().Outbound.Sample(&zerolog.BasicSampler{N: 10}) defer ticker.Stop() @@ -57,38 +60,16 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { Msgf("WatchOutbound: outbound observation is disabled for chain %d", ob.Chain().ChainId) continue } - trackers, err := ob.ZetacoreClient(). - GetAllOutboundTrackerByChain(ctx, ob.Chain().ChainId, interfaces.Ascending) + + // process outbound trackers + err := ob.ProcessOutboundTrackers(ctx) if err != nil { - continue - } - for _, tracker := range trackers { - nonceInt := tracker.Nonce - if ob.IsTxConfirmed(nonceInt) { // Go to next tracker if this one already has a confirmed tx - continue - } - txCount := 0 - var outboundReceipt *ethtypes.Receipt - var outbound *ethtypes.Transaction - for _, txHash := range tracker.HashList { - if receipt, tx, ok := ob.checkConfirmedTx(ctx, txHash.TxHash, nonceInt); ok { - txCount++ - outboundReceipt = receipt - outbound = tx - ob.Logger().Outbound.Info(). - Msgf("WatchOutbound: confirmed outbound %s for chain %d nonce %d", txHash.TxHash, ob.Chain().ChainId, nonceInt) - if txCount > 1 { - ob.Logger().Outbound.Error().Msgf( - "WatchOutbound: checkConfirmedTx passed, txCount %d chain %d nonce %d receipt %v transaction %v", txCount, ob.Chain().ChainId, nonceInt, outboundReceipt, outbound) - } - } - } - if txCount == 1 { // should be only one txHash confirmed for each nonce. - ob.SetTxNReceipt(nonceInt, outboundReceipt, outbound) - } else if txCount > 1 { // should not happen. We can't tell which txHash is true. It might happen (e.g. glitchy/hacked endpoint) - ob.Logger().Outbound.Error().Msgf("WatchOutbound: confirmed multiple (%d) outbound for chain %d nonce %d", txCount, ob.Chain().ChainId, nonceInt) - } + ob.Logger(). + Outbound.Error(). + Err(err). + Msgf("WatchOutbound: error ProcessOutboundTrackers for chain %d", chainID) } + ticker.UpdateInterval(ob.GetChainParams().OutboundTicker, ob.Logger().Outbound) case <-ob.StopChannel(): ob.Logger().Outbound.Info().Msg("WatchOutbound: stopped") @@ -97,6 +78,61 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { } } +// ProcessOutboundTrackers processes outbound trackers +func (ob *Observer) ProcessOutboundTrackers(ctx context.Context) error { + chainID := ob.Chain().ChainId + trackers, err := ob.ZetacoreClient().GetAllOutboundTrackerByChain(ctx, ob.Chain().ChainId, interfaces.Ascending) + if err != nil { + return errors.Wrap(err, "GetAllOutboundTrackerByChain error") + } + + // prepare logger fields + logger := ob.Logger().Outbound.With(). + Str(logs.FieldMethod, "ProcessOutboundTrackers"). + Int64(logs.FieldChain, chainID). + Logger() + + // process outbound trackers + for _, tracker := range trackers { + // go to next tracker if this one already has a confirmed tx + nonce := tracker.Nonce + if ob.IsTxConfirmed(nonce) { + continue + } + + // check each txHash and save tx and receipt if it's legit and confirmed + txCount := 0 + var outboundReceipt *ethtypes.Receipt + var outbound *ethtypes.Transaction + for _, txHash := range tracker.HashList { + if receipt, tx, ok := ob.checkConfirmedTx(ctx, txHash.TxHash, nonce); ok { + txCount++ + outboundReceipt = receipt + outbound = tx + logger.Info().Msgf("confirmed outbound %s for chain %d nonce %d", txHash.TxHash, chainID, nonce) + if txCount > 1 { + logger.Error(). + Msgf("checkConfirmedTx passed, txCount %d chain %d nonce %d receipt %v tx %v", txCount, chainID, nonce, receipt, tx) + } + } + } + + // should be only one txHash confirmed for each nonce. + if txCount == 1 { + ob.SetTxNReceipt(nonce, outboundReceipt, outbound) + } else if txCount > 1 { + // should not happen. We can't tell which txHash is true. It might happen (e.g. bug, glitchy/hacked endpoint) + ob.Logger().Outbound.Error().Msgf("WatchOutbound: confirmed multiple (%d) outbound for chain %d nonce %d", txCount, chainID, nonce) + } else { + if len(tracker.HashList) == crosschainkeeper.MaxOutboundTrackerHashes { + ob.Logger().Outbound.Error().Msgf("WatchOutbound: outbound tracker is full of hashes for chain %d nonce %d", chainID, nonce) + } + } + } + + return nil +} + // PostVoteOutbound posts vote to zetacore for the confirmed outbound func (ob *Observer) PostVoteOutbound( ctx context.Context, @@ -377,23 +413,27 @@ func (ob *Observer) checkConfirmedTx( ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() + // prepare logger + logger := ob.Logger().Outbound.With(). + Str(logs.FieldMethod, "checkConfirmedTx"). + Int64(logs.FieldChain, ob.Chain().ChainId). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, txHash). + Logger() + // query transaction transaction, isPending, err := ob.evmClient.TransactionByHash(ctx, ethcommon.HexToHash(txHash)) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Int64("chainID", ob.Chain().ChainId). - Msg("error getting transaction for outbound") + logger.Error().Err(err).Msg("TransactionByHash error") return nil, nil, false } if transaction == nil { // should not happen - log.Error(). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("nonce", nonce). - Msg("transaction is nil for txHash") + logger.Error().Msg("transaction is nil") + return nil, nil, false + } + if isPending { + // should not happen when we are here. The outbound tracker reporter won't report a pending tx. + logger.Error().Msg("transaction is pending") return nil, nil, false } @@ -401,12 +441,7 @@ func (ob *Observer) checkConfirmedTx( signer := ethtypes.NewLondonSigner(big.NewInt(ob.Chain().ChainId)) from, err := signer.Sender(transaction) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", transaction.Hash().Hex()). - Int64("chainID", ob.Chain().ChainId). - Msg("local recovery of sender address failed for outbound") + logger.Error().Err(err).Msg("local recovery of sender address failed") return nil, nil, false } if from != ob.TSS().EVMAddress() { // must be TSS address @@ -416,13 +451,8 @@ func (ob *Observer) checkConfirmedTx( // TODO : improve this logic to verify that the correct TSS address is the from address. // https://github.com/zeta-chain/node/issues/2487 - log.Info(). - Str("function", "confirmTxByHash"). - Str("sender", from.Hex()). - Str("outboundTxHash", transaction.Hash().Hex()). - Int64("chainID", ob.Chain().ChainId). - Str("currentTSSAddress", ob.TSS().EVMAddress().Hex()). - Msg("sender is not current TSS address") + logger.Warn(). + Msgf("tx sender %s is not matching current TSS address %s", from.String(), ob.TSS().EVMAddress().String()) addressList := ob.TSS().EVMAddressList() isOldTssAddress := false for _, addr := range addressList { @@ -431,70 +461,35 @@ func (ob *Observer) checkConfirmedTx( } } if !isOldTssAddress { - log.Error(). - Str("function", "confirmTxByHash"). - Str("sender", from.Hex()). - Str("outboundTxHash", transaction.Hash().Hex()). - Int64("chainID", ob.Chain().ChainId). - Str("currentTSSAddress", ob.TSS().EVMAddress().Hex()). - Msg("sender is not current or old TSS address") + logger.Error().Msgf("tx sender %s is not matching any of the TSS addresses", from.String()) return nil, nil, false } } - if transaction.Nonce() != nonce { // must match cctx nonce - log.Error(). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("wantedNonce", nonce). - Uint64("gotTxNonce", transaction.Nonce()). - Msg("outbound nonce mismatch") - return nil, nil, false - } - - // save pending transaction - if isPending { - ob.SetPendingTx(nonce, transaction) + if transaction.Nonce() != nonce { // must match tracker nonce + logger.Error().Msgf("tx nonce %d is not matching tracker nonce", nonce) return nil, nil, false } // query receipt receipt, err := ob.evmClient.TransactionReceipt(ctx, ethcommon.HexToHash(txHash)) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("nonce", nonce). - Msg("transactionReceipt error") + logger.Error().Err(err).Msg("TransactionReceipt error") return nil, nil, false } if receipt == nil { // should not happen - log.Error(). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("nonce", nonce). - Msg("receipt is nil") + logger.Error().Msg("receipt is nil") return nil, nil, false } ob.LastBlock() // check confirmations lastHeight, err := ob.evmClient.BlockNumber(ctx) if err != nil { - log.Error(). - Str("function", "confirmTxByHash"). - Err(err). - Int64("chainID", ob.GetChainParams().ChainId). - Msg("error getting block number for chain") + logger.Error().Err(err).Msg("BlockNumber error") return nil, nil, false } if !ob.HasEnoughConfirmations(receipt, lastHeight) { - log.Debug(). - Str("function", "confirmTxByHash"). - Str("txHash", txHash). - Uint64("nonce", nonce). - Uint64("receiptBlock", receipt.BlockNumber.Uint64()). - Uint64("currentBlock", lastHeight). - Msg("txHash included but not confirmed") + logger.Debug(). + Msgf("tx included but not confirmed, receipt block %d current block %d", receipt.BlockNumber.Uint64(), lastHeight) return nil, nil, false } @@ -502,13 +497,7 @@ func (ob *Observer) checkConfirmedTx( // Note: a guard for false BlockNumber in receipt. The blob-carrying tx won't come here err = ob.CheckTxInclusion(transaction, receipt) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("errorContext", "checkTxInclusion"). - Str("txHash", txHash). - Uint64("nonce", nonce). - Msg("checkTxInclusion error") + logger.Error().Err(err).Msg("CheckTxInclusion error") return nil, nil, false } diff --git a/zetaclient/chains/evm/rpc/rpc.go b/zetaclient/chains/evm/rpc/rpc.go new file mode 100644 index 0000000000..6fcc3d007c --- /dev/null +++ b/zetaclient/chains/evm/rpc/rpc.go @@ -0,0 +1,52 @@ +package rpc + +import ( + "context" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" +) + +// IsTxConfirmed checks if the transaction is confirmed with given confirmations +func IsTxConfirmed( + ctx context.Context, + client interfaces.EVMRPCClient, + txHash string, + confirmations uint64, +) (bool, error) { + // query the tx + _, isPending, err := client.TransactionByHash(ctx, ethcommon.HexToHash(txHash)) + if err != nil { + return false, errors.Wrapf(err, "error getting transaction for tx %s", txHash) + } + if isPending { + return false, nil + } + + // query receipt + receipt, err := client.TransactionReceipt(ctx, ethcommon.HexToHash(txHash)) + if err != nil { + return false, errors.Wrapf(err, "error getting transaction receipt for tx %s", txHash) + } + + // should not happen + if receipt == nil { + return false, errors.Errorf("receipt is nil for tx %s", txHash) + } + + // query last block height + lastHeight, err := client.BlockNumber(ctx) + if err != nil { + return false, errors.Wrap(err, "error getting block number") + } + + // check confirmations + if lastHeight < receipt.BlockNumber.Uint64() { + return false, nil + } + blocks := lastHeight - receipt.BlockNumber.Uint64() + 1 + + return blocks >= confirmations, nil +} diff --git a/zetaclient/chains/evm/rpc/rpc_live_test.go b/zetaclient/chains/evm/rpc/rpc_live_test.go new file mode 100644 index 0000000000..0c420c830e --- /dev/null +++ b/zetaclient/chains/evm/rpc/rpc_live_test.go @@ -0,0 +1,45 @@ +package rpc_test + +import ( + "context" + "math" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm/rpc" + + "testing" +) + +const ( + URLEthMainnet = "https://rpc.ankr.com/eth" + URLEthSepolia = "https://rpc.ankr.com/eth_sepolia" + URLBscMainnet = "https://rpc.ankr.com/bsc" + URLPolygonMainnet = "https://rpc.ankr.com/polygon" +) + +// Test_EVMRPCLive is a phony test to run each live test individually +func Test_EVMRPCLive(t *testing.T) { + // LiveTest_IsTxConfirmed(t) +} + +func LiveTest_IsTxConfirmed(t *testing.T) { + client, err := ethclient.Dial(URLEthMainnet) + require.NoError(t, err) + + // check if the transaction is confirmed + ctx := context.Background() + txHash := "0xd2eba7ac3da1b62800165414ea4bcaf69a3b0fb9b13a0fc32f4be11bfef79146" + + t.Run("should confirm tx", func(t *testing.T) { + confirmed, err := rpc.IsTxConfirmed(ctx, client, txHash, 12) + require.NoError(t, err) + require.True(t, confirmed) + }) + + t.Run("should not confirm tx if confirmations is not enough", func(t *testing.T) { + confirmed, err := rpc.IsTxConfirmed(ctx, client, txHash, math.MaxUint64) + require.NoError(t, err) + require.False(t, confirmed) + }) +} diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index 399f4e00e0..dbeef3626d 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -13,7 +13,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" zctx "github.com/zeta-chain/zetacore/zetaclient/context" ) @@ -47,7 +46,6 @@ type OutboundData struct { func NewOutboundData( ctx context.Context, cctx *types.CrossChainTx, - observer *observer.Observer, height uint64, logger zerolog.Logger, ) (*OutboundData, bool, error) { @@ -56,8 +54,6 @@ func NewOutboundData( } outboundParams := cctx.GetCurrentOutboundParam() - nonce := outboundParams.TssNonce - if err := validateParams(outboundParams); err != nil { return nil, false, errors.Wrap(err, "invalid outboundParams") } @@ -88,24 +84,6 @@ func NewOutboundData( return nil, false, errors.Wrap(err, "unable to get cctx index") } - // In case there is a pending tx, make sure this keySign is a tx replacement - if tx := observer.GetPendingTx(nonce); tx != nil { - evt := logger.Info(). - Str("cctx.pending_tx_hash", tx.Hash().Hex()). - Uint64("cctx.pending_tx_nonce", nonce) - - // new gas price is less or equal to pending tx gas - if gas.Price.Cmp(tx.GasPrice()) <= 0 { - evt.Msg("Please wait for pending outbound to be included in the block") - return nil, true, nil - } - - evt. - Uint64("cctx.gas_price", gas.Price.Uint64()). - Uint64("cctx.priority_fee", gas.PriorityFee.Uint64()). - Msg("Replacing pending outbound transaction with higher gas fees") - } - // Base64 decode message var message []byte if cctx.InboundParams.CoinType != coin.CoinType_Cmd { diff --git a/zetaclient/chains/evm/signer/outbound_data_test.go b/zetaclient/chains/evm/signer/outbound_data_test.go index 03c1329ef1..2f53bab028 100644 --- a/zetaclient/chains/evm/signer/outbound_data_test.go +++ b/zetaclient/chains/evm/signer/outbound_data_test.go @@ -13,15 +13,12 @@ import ( ) func TestNewOutboundData(t *testing.T) { - mockObserver, err := getNewEvmChainObserver(t, nil) - require.NoError(t, err) - logger := zerolog.New(zerolog.NewTestWriter(t)) ctx := makeCtx(t) newOutbound := func(cctx *types.CrossChainTx) (*OutboundData, bool, error) { - return NewOutboundData(ctx, cctx, mockObserver, 123, logger) + return NewOutboundData(ctx, cctx, 123, logger) } t.Run("success", func(t *testing.T) { diff --git a/zetaclient/chains/evm/signer/outbound_tracker_reporter.go b/zetaclient/chains/evm/signer/outbound_tracker_reporter.go new file mode 100644 index 0000000000..7a9b6bcfa2 --- /dev/null +++ b/zetaclient/chains/evm/signer/outbound_tracker_reporter.go @@ -0,0 +1,85 @@ +// Package signer implements the ChainSigner interface for EVM chains +package signer + +import ( + "context" + "time" + + "github.com/rs/zerolog" + + "github.com/zeta-chain/zetacore/pkg/bg" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm/rpc" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/logs" +) + +// reportToOutboundTracker reports outboundHash to tracker only when tx receipt is available +func (signer *Signer) reportToOutboundTracker( + ctx context.Context, + zetacoreClient interfaces.ZetacoreClient, + chainID int64, + nonce uint64, + outboundHash string, + logger zerolog.Logger, +) { + // prepare logger + logger = logger.With(). + Str(logs.FieldMethod, "reportToOutboundTracker"). + Int64(logs.FieldChain, chainID). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, outboundHash). + Logger() + + // set being reported flag to avoid duplicate reporting + alreadySet := signer.SetBeingReportedFlag(outboundHash) + if alreadySet { + logger.Info().Msg("outbound is being reported to tracker") + return + } + + // launch a goroutine to monitor tx confirmation status + bg.Work(ctx, func(ctx context.Context) error { + defer func() { + signer.ClearBeingReportedFlag(outboundHash) + }() + + // try monitoring tx inclusion status for 20 minutes + tStart := time.Now() + for { + // take a rest between each check + time.Sleep(10 * time.Second) + + // give up (forget about the tx) after 20 minutes of monitoring, there are 2 reasons: + // 1. the gas stability pool should have kicked in and replaced the tx by then. + // 2. even if there is a chance that the tx is included later, most likely it's going to be a false tx hash (either replaced or dropped). + // 3. we prefer missed tx hash over potentially invalid txhash. + if time.Since(tStart) > evm.OutboundInclusionTimeout { + logger.Info().Msgf("timeout waiting outbound inclusion") + return nil + } + + // check tx confirmation status + confirmed, err := rpc.IsTxConfirmed(ctx, signer.client, outboundHash, evm.ReorgProtectBlockCount) + if err != nil { + logger.Err(err).Msg("unable to check confirmation status of outbound") + continue + } + if !confirmed { + continue + } + + // report outbound hash to tracker + zetaHash, err := zetacoreClient.AddOutboundTracker(ctx, chainID, nonce, outboundHash, nil, "", -1) + if err != nil { + logger.Err(err).Msg("error adding outbound to tracker") + } else if zetaHash != "" { + logger.Info().Msgf("added outbound to tracker; zeta txhash %s", zetaHash) + } else { + // exit goroutine until the tracker contains the hash (reported by either this or other signers) + logger.Info().Msg("outbound now exists in tracker") + return nil + } + } + }, bg.WithName("TrackerReporterEVM"), bg.WithLogger(logger)) +} diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index d30b887ea8..874c118e3e 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -20,19 +20,16 @@ import ( ethrpc "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/pkg/constant" - crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/evm" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/compliance" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/logs" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outboundprocessor" "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" @@ -368,25 +365,6 @@ func (signer *Signer) SignWithdrawTx(ctx context.Context, txData *OutboundData) return tx, nil } -// SignCommandTx signs a transaction based on the given command includes: -// -// cmd_whitelist_erc20 -// cmd_migrate_tss_funds -func (signer *Signer) SignCommandTx( - ctx context.Context, - txData *OutboundData, - cmd string, - params string, -) (*ethtypes.Transaction, error) { - switch cmd { - case constant.CmdWhitelistERC20: - return signer.SignWhitelistERC20Cmd(ctx, txData, params) - case constant.CmdMigrateTssFunds: - return signer.SignMigrateTssFundsCmd(ctx, txData) - } - return nil, fmt.Errorf("SignCommandTx: unknown command %s", cmd) -} - // TryProcessOutbound - signer interface implementation // This function will attempt to build and sign an evm transaction using the TSS signer. // It will then broadcast the signed transaction to the outbound chain. @@ -396,48 +374,42 @@ func (signer *Signer) TryProcessOutbound( cctx *types.CrossChainTx, outboundProc *outboundprocessor.Processor, outboundID string, - chainObserver interfaces.ChainObserver, + _ interfaces.ChainObserver, zetacoreClient interfaces.ZetacoreClient, height uint64, ) { + // end outbound process on panic + defer func() { + outboundProc.EndTryProcess(outboundID) + if r := recover(); r != nil { + signer.Logger().Std.Error().Msgf("TryProcessOutbound: %s, caught panic error: %v", cctx.Index, r) + } + }() + + // prepare logger and a few local variables var ( params = cctx.GetCurrentOutboundParam() myID = zetacoreClient.GetKeys().GetOperatorAddress() logger = signer.Logger().Std.With(). - Str("method", "TryProcessOutbound"). - Int64("chain", signer.Chain().ChainId). - Uint64("nonce", params.TssNonce). - Str("cctx.index", cctx.Index). + Str(logs.FieldMethod, "TryProcessOutbound"). + Int64(logs.FieldChain, signer.Chain().ChainId). + Uint64(logs.FieldNonce, params.TssNonce). + Str(logs.FieldCctx, cctx.Index). Str("cctx.receiver", params.Receiver). Str("cctx.amount", params.Amount.String()). Logger() ) - logger.Info().Msgf("TryProcessOutbound") + // retrieve app context app, err := zctx.FromContext(ctx) if err != nil { - signer.Logger().Std.Error().Err(err).Msg("error getting app context") - return - } - - // end outbound process on panic - defer func() { - if r := recover(); r != nil { - logger.Error().Interface("panic", r).Msg("panic in TryProcessOutbound") - } - - outboundProc.EndTryProcess(outboundID) - }() - - evmObserver, ok := chainObserver.(*observer.Observer) - if !ok { - logger.Error().Msg("chain observer is not an EVM observer") + logger.Error().Err(err).Msg("error getting app context") return } // Setup Transaction input - txData, skipTx, err := NewOutboundData(ctx, cctx, evmObserver, height, logger) + txData, skipTx, err := NewOutboundData(ctx, cctx, height, logger) if err != nil { logger.Err(err).Msg("error setting up transaction input fields") return @@ -495,7 +467,7 @@ func (signer *Signer) TryProcessOutbound( // params field is used to pass input parameters for command requests, currently it is used to pass the ERC20 // contract address when a whitelist command is requested params := msg[1] - tx, err = signer.SignCommandTx(ctx, txData, cmd, params) + tx, err = signer.SignAdminTx(ctx, txData, cmd, params) if err != nil { logger.Warn().Err(err).Msg(ErrorMsg(cctx)) return @@ -733,172 +705,6 @@ func ErrorMsg(cctx *types.CrossChainTx) string { ) } -// SignWhitelistERC20Cmd signs a whitelist command for ERC20 token -// TODO(revamp): move the cmd in a specific file -func (signer *Signer) SignWhitelistERC20Cmd( - ctx context.Context, - txData *OutboundData, - params string, -) (*ethtypes.Transaction, error) { - outboundParams := txData.outboundParams - erc20 := ethcommon.HexToAddress(params) - if erc20 == (ethcommon.Address{}) { - return nil, fmt.Errorf("SignCommandTx: invalid erc20 address %s", params) - } - - custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() - if err != nil { - return nil, err - } - - data, err := custodyAbi.Pack("whitelist", erc20) - if err != nil { - return nil, fmt.Errorf("whitelist pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - txData.to, - zeroValue, - txData.gas, - outboundParams.TssNonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign whitelist error: %w", err) - } - - return tx, nil -} - -// SignMigrateTssFundsCmd signs a migrate TSS funds command -// TODO(revamp): move the cmd in a specific file -func (signer *Signer) SignMigrateTssFundsCmd(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - tx, _, _, err := signer.Sign( - ctx, - nil, - txData.to, - txData.amount, - txData.gas, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("SignMigrateTssFundsCmd error: %w", err) - } - return tx, nil -} - -// reportToOutboundTracker reports outboundHash to tracker only when tx receipt is available -// TODO(revamp): move outbound tracker function to a outbound tracker file -func (signer *Signer) reportToOutboundTracker( - ctx context.Context, - zetacoreClient interfaces.ZetacoreClient, - chainID int64, - nonce uint64, - outboundHash string, - logger zerolog.Logger, -) { - // set being reported flag to avoid duplicate reporting - alreadySet := signer.Signer.SetBeingReportedFlag(outboundHash) - if alreadySet { - logger.Info(). - Msgf("reportToOutboundTracker: outboundHash %s for chain %d nonce %d is being reported", outboundHash, chainID, nonce) - return - } - - // report to outbound tracker with goroutine - go func() { - defer func() { - signer.Signer.ClearBeingReportedFlag(outboundHash) - }() - - // try monitoring tx inclusion status for 10 minutes - var err error - report := false - isPending := false - blockNumber := uint64(0) - tStart := time.Now() - for { - // give up after 10 minutes of monitoring - time.Sleep(10 * time.Second) - - if time.Since(tStart) > evm.OutboundInclusionTimeout { - // if tx is still pending after timeout, report to outboundTracker anyway as we cannot monitor forever - if isPending { - report = true // probably will be included later - } - logger.Info(). - Msgf("reportToOutboundTracker: timeout waiting tx inclusion for chain %d nonce %d outboundHash %s report %v", chainID, nonce, outboundHash, report) - break - } - // try getting the tx - _, isPending, err = signer.client.TransactionByHash(ctx, ethcommon.HexToHash(outboundHash)) - if err != nil { - logger.Info(). - Err(err). - Msgf("reportToOutboundTracker: error getting tx for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - continue - } - // if tx is include in a block, try getting receipt - if !isPending { - report = true // included - receipt, err := signer.client.TransactionReceipt(ctx, ethcommon.HexToHash(outboundHash)) - if err != nil { - logger.Info(). - Err(err). - Msgf("reportToOutboundTracker: error getting receipt for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } - if receipt != nil { - blockNumber = receipt.BlockNumber.Uint64() - } - break - } - // keep monitoring pending tx - logger.Info(). - Msgf("reportToOutboundTracker: tx has not been included yet for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } - - // try adding to outbound tracker for 10 minutes - if report { - tStart := time.Now() - for { - // give up after 10 minutes of retrying - if time.Since(tStart) > evm.OutboundTrackerReportTimeout { - logger.Info(). - Msgf("reportToOutboundTracker: timeout adding outbound tracker for chain %d nonce %d outboundHash %s, please add manually", chainID, nonce, outboundHash) - break - } - // stop if the cctx is already finalized - cctx, err := zetacoreClient.GetCctxByNonce(ctx, chainID, nonce) - if err != nil { - logger.Err(err). - Msgf("reportToOutboundTracker: error getting cctx for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } else if !crosschainkeeper.IsPending(cctx) { - logger.Info().Msgf("reportToOutboundTracker: cctx already finalized for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - break - } - // report to outbound tracker - zetaHash, err := zetacoreClient.AddOutboundTracker(ctx, chainID, nonce, outboundHash, nil, "", -1) - if err != nil { - logger.Err(err). - Msgf("reportToOutboundTracker: error adding to outbound tracker for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } else if zetaHash != "" { - logger.Info().Msgf("reportToOutboundTracker: added outboundHash to core successful %s, chain %d nonce %d outboundHash %s block %d", - zetaHash, chainID, nonce, outboundHash, blockNumber) - } else { - // stop if the tracker contains the outboundHash - logger.Info().Msgf("reportToOutboundTracker: outbound tracker contains outboundHash %s for chain %d nonce %d", outboundHash, chainID, nonce) - break - } - // retry otherwise - time.Sleep(evm.ZetaBlockTime * 3) - } - } - }() -} - // getEVMRPC is a helper function to set up the client and signer, also initializes a mock client for unit tests func getEVMRPC(ctx context.Context, endpoint string) (interfaces.EVMRPCClient, ethtypes.Signer, error) { if endpoint == mocks.EVMRPCEnabled { diff --git a/zetaclient/chains/evm/signer/signer_admin.go b/zetaclient/chains/evm/signer/signer_admin.go new file mode 100644 index 0000000000..b52e6a769b --- /dev/null +++ b/zetaclient/chains/evm/signer/signer_admin.go @@ -0,0 +1,128 @@ +package signer + +import ( + "context" + "fmt" + "math/big" + "strings" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" + + "github.com/zeta-chain/zetacore/pkg/constant" +) + +// SignAdminTx signs a transaction based on the given command includes: +// +// cmd_whitelist_erc20 +// cmd_migrate_erc20_custody_funds +// cmd_migrate_tss_funds +func (signer *Signer) SignAdminTx( + ctx context.Context, + txData *OutboundData, + cmd string, + params string, +) (*ethtypes.Transaction, error) { + switch cmd { + case constant.CmdWhitelistERC20: + return signer.signWhitelistERC20Cmd(ctx, txData, params) + case constant.CmdMigrateERC20CustodyFunds: + return signer.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + case constant.CmdMigrateTssFunds: + return signer.signMigrateTssFundsCmd(ctx, txData) + } + return nil, fmt.Errorf("SignAdminTx: unknown command %s", cmd) +} + +// signWhitelistERC20Cmd signs a whitelist command for ERC20 token +func (signer *Signer) signWhitelistERC20Cmd( + ctx context.Context, + txData *OutboundData, + params string, +) (*ethtypes.Transaction, error) { + erc20 := ethcommon.HexToAddress(params) + if erc20 == (ethcommon.Address{}) { + return nil, fmt.Errorf("SignAdminTx: invalid erc20 address %s", params) + } + custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, err + } + data, err := custodyAbi.Pack("whitelist", erc20) + if err != nil { + return nil, fmt.Errorf("whitelist pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + txData.to, + zeroValue, + txData.gas, + txData.outboundParams.TssNonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign whitelist error: %w", err) + } + return tx, nil +} + +// signMigrateERC20CustodyFundsCmd signs a migrate ERC20 custody funds command +func (signer *Signer) signMigrateERC20CustodyFundsCmd( + ctx context.Context, + txData *OutboundData, + params string, +) (*ethtypes.Transaction, error) { + paramsArray := strings.Split(params, ",") + if len(paramsArray) != 3 { + return nil, fmt.Errorf("signMigrateERC20CustodyFundsCmd: invalid params %s", params) + } + newCustody := ethcommon.HexToAddress(paramsArray[0]) + erc20 := ethcommon.HexToAddress(paramsArray[1]) + amount, ok := new(big.Int).SetString(paramsArray[2], 10) + if !ok { + return nil, fmt.Errorf("signMigrateERC20CustodyFundsCmd: invalid amount %s", paramsArray[2]) + } + + custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, err + } + data, err := custodyAbi.Pack("withdraw", newCustody, erc20, amount) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + txData.to, + zeroValue, + txData.gas, + txData.outboundParams.TssNonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("signMigrateERC20CustodyFundsCmd error: %w", err) + } + return tx, nil +} + +// signMigrateTssFundsCmd signs a migrate TSS funds command +func (signer *Signer) signMigrateTssFundsCmd(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + tx, _, _, err := signer.Sign( + ctx, + nil, + txData.to, + txData.amount, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("signMigrateTssFundsCmd error: %w", err) + } + return tx, nil +} diff --git a/zetaclient/chains/evm/signer/signer_admin_test.go b/zetaclient/chains/evm/signer/signer_admin_test.go new file mode 100644 index 0000000000..820b521892 --- /dev/null +++ b/zetaclient/chains/evm/signer/signer_admin_test.go @@ -0,0 +1,231 @@ +package signer + +import ( + "fmt" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" + "math/big" + "testing" +) + +func TestSigner_SignAdminTx(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + evmSigner, err := getNewEvmSigner(nil) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.False(t, skip) + require.NoError(t, err) + + t.Run("SignAdminTx CmdWhitelistERC20", func(t *testing.T) { + cmd := constant.CmdWhitelistERC20 + params := ConnectorAddress.Hex() + // Call SignAdminTx + tx, err := evmSigner.SignAdminTx(ctx, txData, cmd, params) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Revert tx calls erc20 custody contract with 0 gas token + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, big.NewInt(0)) + }) + + t.Run("SignAdminTx CmdMigrateERC20CustodyFunds", func(t *testing.T) { + cmd := constant.CmdMigrateERC20CustodyFunds + params := fmt.Sprintf( + "%s,%s,%s", + sample.EthAddress().Hex(), + sample.EthAddress().Hex(), + big.NewInt(100).String(), + ) + // Call SignAdminTx + tx, err := evmSigner.SignAdminTx(ctx, txData, cmd, params) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Revert tx calls erc20 custody contract with 0 gas token + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, big.NewInt(0)) + }) + + t.Run("SignAdminTx CmdMigrateTssFunds", func(t *testing.T) { + cmd := constant.CmdMigrateTssFunds + // Call SignAdminTx + tx, err := evmSigner.SignAdminTx(ctx, txData, cmd, "") + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) + }) +} + +func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + + require.NoError(t, err) + + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.NoError(t, err) + require.False(t, skip) + + t.Run("signWhitelistERC20Cmd - should successfully sign", func(t *testing.T) { + // Call signWhitelistERC20Cmd + tx, err := evmSigner.signWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) + }) + + t.Run("signWhitelistERC20Cmd - should fail on invalid erc20 address", func(t *testing.T) { + tx, err := evmSigner.signWhitelistERC20Cmd(ctx, txData, "") + require.Nil(t, tx) + require.ErrorContains(t, err, "invalid erc20 address") + }) + + t.Run("signWhitelistERC20Cmd - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call signWhitelistERC20Cmd + tx, err := evmSigner.signWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) + require.ErrorContains(t, err, "sign whitelist error") + require.Nil(t, tx) + }) +} + +func TestSigner_SignMigrateERC20CustodyFundsCmd(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + + require.NoError(t, err) + + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.NoError(t, err) + require.False(t, skip) + + t.Run("signMigrateERC20CustodyFundsCmd - should successfully sign", func(t *testing.T) { + // Call signWhitelistERC20Cmd + + params := fmt.Sprintf( + "%s,%s,%s", + sample.EthAddress().Hex(), + sample.EthAddress().Hex(), + big.NewInt(100).String(), + ) + + tx, err := evmSigner.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) + }) + + t.Run("signMigrateERC20CustodyFundsCmd - should fail on invalid params", func(t *testing.T) { + + params := fmt.Sprintf("%s,%s", sample.EthAddress().Hex(), sample.EthAddress().Hex()) + + _, err := evmSigner.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + require.ErrorContains(t, err, "invalid params") + }) + + t.Run("signMigrateERC20CustodyFundsCmd - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + params := fmt.Sprintf( + "%s,%s,%s", + sample.EthAddress().Hex(), + sample.EthAddress().Hex(), + big.NewInt(100).String(), + ) + + // Call signWhitelistERC20Cmd + tx, err := evmSigner.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + require.ErrorContains(t, err, "tss is paused") + require.Nil(t, tx) + }) +} + +func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.False(t, skip) + require.NoError(t, err) + + t.Run("signMigrateTssFundsCmd - should successfully sign", func(t *testing.T) { + // Call signMigrateTssFundsCmd + tx, err := evmSigner.signMigrateTssFundsCmd(ctx, txData) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) + }) + + t.Run("signMigrateTssFundsCmd - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call signMigrateTssFundsCmd + tx, err := evmSigner.signMigrateTssFundsCmd(ctx, txData) + require.ErrorContains(t, err, "signMigrateTssFundsCmd error") + require.Nil(t, tx) + }) +} diff --git a/zetaclient/chains/evm/signer/signer_test.go b/zetaclient/chains/evm/signer/signer_test.go index 9233dfcbe2..570cd57042 100644 --- a/zetaclient/chains/evm/signer/signer_test.go +++ b/zetaclient/chains/evm/signer/signer_test.go @@ -18,7 +18,6 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/keys" "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/constant" "github.com/zeta-chain/zetacore/testutil/sample" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" @@ -196,9 +195,7 @@ func TestSigner_SignOutbound(t *testing.T) { // Setup txData struct cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, makeLogger(t)) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -238,7 +235,7 @@ func TestSigner_SignOutbound(t *testing.T) { cctx.OutboundParams[0].GasPriorityFee = big.NewInt(priorityFee).String() // Given outbound data - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, makeLogger(t)) + txData, skip, err := NewOutboundData(ctx, cctx, 123, makeLogger(t)) require.False(t, skip) require.NoError(t, err) @@ -271,9 +268,7 @@ func TestSigner_SignRevertTx(t *testing.T) { // Setup txData struct cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -311,9 +306,7 @@ func TestSigner_SignCancelTx(t *testing.T) { // Setup txData struct cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -351,9 +344,7 @@ func TestSigner_SignWithdrawTx(t *testing.T) { // Setup txData struct cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -380,52 +371,6 @@ func TestSigner_SignWithdrawTx(t *testing.T) { }) } -func TestSigner_SignCommandTx(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - evmSigner, err := getNewEvmSigner(nil) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, nil) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignCommandTx CmdWhitelistERC20", func(t *testing.T) { - cmd := constant.CmdWhitelistERC20 - params := ConnectorAddress.Hex() - // Call SignCommandTx - tx, err := evmSigner.SignCommandTx(ctx, txData, cmd, params) - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - // Note: Revert tx calls erc20 custody contract with 0 gas token - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, big.NewInt(0)) - }) - - t.Run("SignCommandTx CmdMigrateTssFunds", func(t *testing.T) { - cmd := constant.CmdMigrateTssFunds - // Call SignCommandTx - tx, err := evmSigner.SignCommandTx(ctx, txData, cmd, "") - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) - }) -} - func TestSigner_SignERC20WithdrawTx(t *testing.T) { ctx := makeCtx(t) @@ -436,9 +381,7 @@ func TestSigner_SignERC20WithdrawTx(t *testing.T) { // Setup txData struct cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.False(t, skip) require.NoError(t, err) @@ -476,10 +419,7 @@ func TestSigner_BroadcastOutbound(t *testing.T) { // Setup txData struct cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, nil) - require.NoError(t, err) - - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.NoError(t, err) require.False(t, skip) @@ -522,93 +462,6 @@ func TestSigner_SignerErrorMsg(t *testing.T) { require.Contains(t, msg, "nonce 68270 chain 56") } -func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.NoError(t, err) - require.False(t, skip) - - t.Run("SignWhitelistERC20Cmd - should successfully sign", func(t *testing.T) { - // Call SignWhitelistERC20Cmd - tx, err := evmSigner.SignWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) - require.NoError(t, err) - require.NotNil(t, tx) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) - }) - t.Run("SignWhitelistERC20Cmd - should fail on invalid erc20 address", func(t *testing.T) { - tx, err := evmSigner.SignWhitelistERC20Cmd(ctx, txData, "") - require.Nil(t, tx) - require.ErrorContains(t, err, "invalid erc20 address") - }) - t.Run("SignWhitelistERC20Cmd - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignWhitelistERC20Cmd - tx, err := evmSigner.SignWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) - require.ErrorContains(t, err, "sign whitelist error") - require.Nil(t, tx) - }) -} - -func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignMigrateTssFundsCmd - should successfully sign", func(t *testing.T) { - // Call SignMigrateTssFundsCmd - tx, err := evmSigner.SignMigrateTssFundsCmd(ctx, txData) - require.NoError(t, err) - require.NotNil(t, tx) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) - }) - - t.Run("SignMigrateTssFundsCmd - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignMigrateTssFundsCmd - tx, err := evmSigner.SignMigrateTssFundsCmd(ctx, txData) - require.ErrorContains(t, err, "SignMigrateTssFundsCmd error") - require.Nil(t, tx) - }) -} func makeCtx(t *testing.T) context.Context { app := zctx.New(config.New(false), zerolog.Nop()) diff --git a/zetaclient/chains/solana/observer/outbound.go b/zetaclient/chains/solana/observer/outbound.go index 6eca0b437e..396e49df1c 100644 --- a/zetaclient/chains/solana/observer/outbound.go +++ b/zetaclient/chains/solana/observer/outbound.go @@ -17,6 +17,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/logs" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "github.com/zeta-chain/zetacore/zetaclient/zetacore" ) @@ -254,15 +255,15 @@ func (ob *Observer) CheckFinalizedTx( // prepare logger fields chainID := ob.Chain().ChainId logger := ob.Logger().Outbound.With(). - Str("method", "checkFinalizedTx"). - Int64("chain", chainID). - Uint64("nonce", nonce). - Str("tx", txHash).Logger() + Str(logs.FieldMethod, "CheckFinalizedTx"). + Int64(logs.FieldChain, chainID). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, txHash).Logger() // convert txHash to signature sig, err := solana.SignatureFromBase58(txHash) if err != nil { - logger.Error().Err(err).Msgf("SignatureFromBase58 err for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("SignatureFromBase58 error") return nil, false } @@ -271,20 +272,20 @@ func (ob *Observer) CheckFinalizedTx( Commitment: rpc.CommitmentFinalized, }) if err != nil { - logger.Error().Err(err).Msgf("GetTransaction err for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("GetTransaction error") return nil, false } // the tx must be successful in order to effectively increment the nonce if txResult.Meta.Err != nil { - logger.Error().Any("Err", txResult.Meta.Err).Msgf("tx is not successful for chain %d nonce %d", chainID, nonce) + logger.Error().Any("Err", txResult.Meta.Err).Msg("tx is not successful") return nil, false } // parse gateway instruction from tx result inst, err := ParseGatewayInstruction(txResult, ob.gatewayID, coinType) if err != nil { - logger.Error().Err(err).Msgf("ParseGatewayInstruction err for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("ParseGatewayInstruction error") return nil, false } txNonce := inst.GatewayNonce() @@ -292,19 +293,19 @@ func (ob *Observer) CheckFinalizedTx( // recover ECDSA signer from instruction signerECDSA, err := inst.Signer() if err != nil { - logger.Error().Err(err).Msgf("cannot get instruction signer for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("cannot get instruction signer") return nil, false } // check tx authorization if signerECDSA != ob.TSS().EVMAddress() { - logger.Error().Msgf("tx signer %s is not matching TSS, chain %d nonce %d", signerECDSA, chainID, nonce) + logger.Error().Msgf("tx signer %s is not matching current TSS address %s", signerECDSA, ob.TSS().EVMAddress()) return nil, false } // check tx nonce if txNonce != nonce { - logger.Error().Msgf("tx nonce %d is not matching cctx, chain %d nonce %d", txNonce, chainID, nonce) + logger.Error().Msgf("tx nonce %d is not matching tracker nonce", txNonce) return nil, false } diff --git a/zetaclient/chains/solana/signer/outbound_tracker_reporter.go b/zetaclient/chains/solana/signer/outbound_tracker_reporter.go index 6462060beb..0a5fb6432e 100644 --- a/zetaclient/chains/solana/signer/outbound_tracker_reporter.go +++ b/zetaclient/chains/solana/signer/outbound_tracker_reporter.go @@ -8,7 +8,9 @@ import ( "github.com/gagliardetto/solana-go/rpc" "github.com/rs/zerolog" + "github.com/zeta-chain/zetacore/pkg/bg" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/logs" ) const ( @@ -27,16 +29,23 @@ func (signer *Signer) reportToOutboundTracker( txSig solana.Signature, logger zerolog.Logger, ) { + // prepare logger + logger = logger.With(). + Str(logs.FieldMethod, "reportToOutboundTracker"). + Int64(logs.FieldChain, chainID). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, txSig.String()). + Logger() + // set being reported flag to avoid duplicate reporting alreadySet := signer.Signer.SetBeingReportedFlag(txSig.String()) if alreadySet { - logger.Info(). - Msgf("reportToOutboundTracker: outbound %s for chain %d nonce %d is being reported", txSig, chainID, nonce) + logger.Info().Msg("outbound is being reported to tracker") return } // launch a goroutine to monitor tx confirmation status - go func() { + bg.Work(ctx, func(ctx context.Context) error { defer func() { signer.Signer.ClearBeingReportedFlag(txSig.String()) }() @@ -48,9 +57,8 @@ func (signer *Signer) reportToOutboundTracker( // give up if we know the tx is too old and already expired if time.Since(start) > SolanaTransactionTimeout { - logger.Info(). - Msgf("reportToOutboundTracker: outbound %s expired for chain %d nonce %d", txSig, chainID, nonce) - return + logger.Info().Msg("outbound is expired") + return nil } // query tx using optimistic commitment level "confirmed" @@ -68,24 +76,21 @@ func (signer *Signer) reportToOutboundTracker( // unlike Ethereum, Solana doesn't have protocol-level nonce; the nonce is enforced by the gateway program. // a failed outbound (e.g. signature err, balance err) will never be able to increment the gateway program nonce. // a good/valid candidate of outbound tracker hash must come with a successful tx. - logger.Warn(). - Any("Err", tx.Meta.Err). - Msgf("reportToOutboundTracker: outbound %s failed for chain %d nonce %d", txSig, chainID, nonce) - return + logger.Warn().Any("Err", tx.Meta.Err).Msg("outbound is failed") + return nil } // report outbound hash to zetacore zetaHash, err := zetacoreClient.AddOutboundTracker(ctx, chainID, nonce, txSig.String(), nil, "", -1) if err != nil { - logger.Err(err). - Msgf("reportToOutboundTracker: error adding outbound %s for chain %d nonce %d", txSig, chainID, nonce) + logger.Err(err).Msg("error adding outbound to tracker") } else if zetaHash != "" { - logger.Info().Msgf("reportToOutboundTracker: added outbound %s for chain %d nonce %d; zeta txhash %s", txSig, chainID, nonce, zetaHash) + logger.Info().Msgf("added outbound to tracker; zeta txhash %s", zetaHash) } else { - // exit goroutine if the tracker already contains the hash (reported by other signer) - logger.Info().Msgf("reportToOutboundTracker: outbound %s already in tracker for chain %d nonce %d", txSig, chainID, nonce) - return + // exit goroutine until the tracker contains the hash (reported by either this or other signers) + logger.Info().Msg("outbound now exists in tracker") + return nil } } - }() + }, bg.WithName("TrackerReporterSolana"), bg.WithLogger(logger)) } diff --git a/zetaclient/logs/fields.go b/zetaclient/logs/fields.go new file mode 100644 index 0000000000..497690ffa4 --- /dev/null +++ b/zetaclient/logs/fields.go @@ -0,0 +1,18 @@ +package logs + +// A group of predefined field keys and module names for zetaclient logs +const ( + // field keys + FieldModule = "module" + FieldMethod = "method" + FieldChain = "chain" + FieldNonce = "nonce" + FieldTx = "tx" + FieldCctx = "cctx" + + // module names + ModNameInbound = "inbound" + ModNameOutbound = "outbound" + ModNameGasPrice = "gasprice" + ModNameHeaders = "headers" +) diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index 884cbdc485..a7f8feccfb 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -15,12 +15,12 @@ import ( "github.com/samber/lo" "github.com/zeta-chain/zetacore/pkg/bg" + "github.com/zeta-chain/zetacore/pkg/constant" zetamath "github.com/zeta-chain/zetacore/pkg/math" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" solanaobserver "github.com/zeta-chain/zetacore/zetaclient/chains/solana/observer" zctx "github.com/zeta-chain/zetacore/zetaclient/context" @@ -660,8 +660,13 @@ func (oc *Orchestrator) ScheduleCctxSolana( // runObserverSignerSync runs a blocking ticker that observes chain changes from zetacore // and optionally (de)provisions respective observers and signers. func (oc *Orchestrator) runObserverSignerSync(ctx context.Context) error { - // check every other zeta block - const cadence = 2 * evm.ZetaBlockTime + // sync observers and signers right away to speed up zetaclient startup + if err := oc.syncObserverSigner(ctx); err != nil { + oc.logger.Error().Err(err).Msg("runObserverSignerSync: syncObserverSigner failed for initial sync") + } + + // sync observer and signer every 10 blocks (approx. 1 minute) + const cadence = 10 * constant.ZetaBlockTime ticker := time.NewTicker(cadence) defer ticker.Stop()