From 7b34d1d2760cfb7985a1219e35173ffb4f3095cb Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Fri, 30 Aug 2024 04:56:06 -0500 Subject: [PATCH 1/2] fix: ask for 3 accounts (signer, pda, system_program) when parsing Sonala inbound signer address (#2787) * explicitly ask for 3 accounts on solana gateway deposit; use default relayer key path if not set * add changelog entry and revert the modification on localnet docker file * add unit test for default relayer key path * move changelog entry to Fixes section --- changelog.md | 1 + cmd/zetaclientd/import_relayer_keys.go | 4 +- e2e/runner/solana.go | 1 - pkg/contracts/solana/gateway.go | 4 +- zetaclient/chains/solana/observer/inbound.go | 17 ++--- .../chains/solana/observer/inbound_test.go | 28 ++++---- zetaclient/config/types.go | 8 +++ zetaclient/config/types_test.go | 16 +++++ ...LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk.json | 64 ------------------- ...axBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json | 64 +++++++++++++++++++ zetaclient/testutils/constant.go | 2 +- 11 files changed, 117 insertions(+), 92 deletions(-) create mode 100644 zetaclient/config/types_test.go delete mode 100644 zetaclient/testdata/solana/chain_901_inbound_tx_result_5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk.json create mode 100644 zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json diff --git a/changelog.md b/changelog.md index 53d03c8805..56e002b735 100644 --- a/changelog.md +++ b/changelog.md @@ -34,6 +34,7 @@ * [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module * [2674](https://github.com/zeta-chain/node/pull/2674) - allow operators to vote on ballots associated with discarded keygen without affecting the status of the current keygen. * [2672](https://github.com/zeta-chain/node/pull/2672) - check observer set for duplicates when adding a new observer or updating an existing one +* [2787](https://github.com/zeta-chain/node/pull/2787) - ask for 3 accounts (signer, pda, system_program) on solana gateway deposit ## v19.0.0 diff --git a/cmd/zetaclientd/import_relayer_keys.go b/cmd/zetaclientd/import_relayer_keys.go index caf2db9538..b624ac7afb 100644 --- a/cmd/zetaclientd/import_relayer_keys.go +++ b/cmd/zetaclientd/import_relayer_keys.go @@ -12,6 +12,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/crypto" zetaos "github.com/zeta-chain/zetacore/pkg/os" + "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/keys" ) @@ -52,8 +53,7 @@ func init() { RootCmd.AddCommand(CmdRelayerAddress) // resolve default relayer key path - defaultRelayerKeyPath := "~/.zetacored/relayer-keys" - defaultRelayerKeyPath, err := zetaos.ExpandHomeDir(defaultRelayerKeyPath) + defaultRelayerKeyPath, err := zetaos.ExpandHomeDir(config.DefaultRelayerKeyPath) if err != nil { log.Fatal().Err(err).Msg("failed to resolve default relayer key path") } diff --git a/e2e/runner/solana.go b/e2e/runner/solana.go index 30e089406e..24685d3b61 100644 --- a/e2e/runner/solana.go +++ b/e2e/runner/solana.go @@ -44,7 +44,6 @@ func (r *E2ERunner) CreateDepositInstruction( accountSlice = append(accountSlice, solana.Meta(signer).WRITE().SIGNER()) accountSlice = append(accountSlice, solana.Meta(pdaComputed).WRITE()) accountSlice = append(accountSlice, solana.Meta(solana.SystemProgramID)) - accountSlice = append(accountSlice, solana.Meta(programID)) inst.ProgID = programID inst.AccountValues = accountSlice diff --git a/pkg/contracts/solana/gateway.go b/pkg/contracts/solana/gateway.go index 356614e4ce..a8f0c571e5 100644 --- a/pkg/contracts/solana/gateway.go +++ b/pkg/contracts/solana/gateway.go @@ -14,8 +14,8 @@ const ( PDASeed = "meta" // AccountsNumberOfDeposit is the number of accounts required for Solana gateway deposit instruction - // [signer, pda, system_program, gateway_program] - AccountsNumDeposit = 4 + // [signer, pda, system_program] + AccountsNumDeposit = 3 ) // DiscriminatorInitialize returns the discriminator for Solana gateway 'initialize' instruction diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go index ef459cbd7a..4f2eccbd35 100644 --- a/zetaclient/chains/solana/observer/inbound.go +++ b/zetaclient/chains/solana/observer/inbound.go @@ -285,10 +285,13 @@ func (ob *Observer) ParseInboundAsDeposit( return nil, nil } - // get the sender address (the signer must exist) + // get the sender address (skip if unable to parse signer address) sender, err := ob.GetSignerDeposit(tx, &instruction) if err != nil { - return nil, errors.Wrap(err, "error GetSignerDeposit") + ob.Logger(). + Inbound.Err(err). + Msgf("unable to get signer for sig %s instruction %d", tx.Signatures[0], instructionIndex) + return nil, nil } // build inbound event @@ -323,13 +326,13 @@ func (ob *Observer) ParseInboundAsDepositSPL( // GetSignerDeposit returns the signer address of the deposit instruction // Note: solana-go is not able to parse the AccountMeta 'is_signer' ATM. This is a workaround. func (ob *Observer) GetSignerDeposit(tx *solana.Transaction, inst *solana.CompiledInstruction) (string, error) { - // there should be 4 accounts for a deposit instruction + // there should be 3 accounts for a deposit instruction if len(inst.Accounts) != solanacontracts.AccountsNumDeposit { return "", fmt.Errorf("want %d accounts, got %d", solanacontracts.AccountsNumDeposit, len(inst.Accounts)) } - // the accounts are [signer, pda, system_program, gateway_program] - signerIndex, pdaIndex, systemIndex, gatewayIndex := -1, -1, -1, -1 + // the accounts are [signer, pda, system_program] + signerIndex, pdaIndex, systemIndex := -1, -1, -1 // try to find the indexes of all above accounts for _, accIndex := range inst.Accounts { @@ -340,8 +343,6 @@ func (ob *Observer) GetSignerDeposit(tx *solana.Transaction, inst *solana.Compil switch accKey { case ob.pda: pdaIndex = accIndexInt - case ob.gatewayID: - gatewayIndex = accIndexInt case solana.SystemProgramID: systemIndex = accIndexInt default: @@ -351,7 +352,7 @@ func (ob *Observer) GetSignerDeposit(tx *solana.Transaction, inst *solana.Compil } // all above accounts must be found - if signerIndex == -1 || pdaIndex == -1 || systemIndex == -1 || gatewayIndex == -1 { + if signerIndex == -1 || pdaIndex == -1 || systemIndex == -1 { return "", fmt.Errorf("invalid accounts for deposit instruction") } diff --git a/zetaclient/chains/solana/observer/inbound_test.go b/zetaclient/chains/solana/observer/inbound_test.go index 40f53ce0bc..2ead330d12 100644 --- a/zetaclient/chains/solana/observer/inbound_test.go +++ b/zetaclient/chains/solana/observer/inbound_test.go @@ -26,8 +26,8 @@ var ( func Test_FilterInboundEventAndVote(t *testing.T) { // load archived inbound vote tx result - // https://explorer.solana.com/tx/5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk?cluster=devnet - txHash := "5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk" + // https://explorer.solana.com/tx/MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j?cluster=devnet + txHash := "MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j" chain := chains.SolanaDevnet txResult := testutils.LoadSolanaInboundTxResult(t, TestDataDir, chain.ChainId, txHash, false) @@ -51,8 +51,8 @@ func Test_FilterInboundEventAndVote(t *testing.T) { func Test_FilterInboundEvents(t *testing.T) { // load archived inbound deposit tx result - // https://explorer.solana.com/tx/5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk?cluster=devnet - txHash := "5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk" + // https://explorer.solana.com/tx/MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j?cluster=devnet + txHash := "MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j" chain := chains.SolanaDevnet txResult := testutils.LoadSolanaInboundTxResult(t, TestDataDir, chain.ChainId, txHash, false) @@ -61,20 +61,20 @@ func Test_FilterInboundEvents(t *testing.T) { // create observer chainParams := sample.ChainParams(chain.ChainId) - chainParams.GatewayAddress = GatewayAddressTest + chainParams.GatewayAddress = testutils.GatewayAddresses[chain.ChainId] ob, err := observer.NewObserver(chain, nil, *chainParams, nil, nil, database, base.DefaultLogger(), nil) require.NoError(t, err) // expected result - sender := "AKbG83jg2V65R7XvaPFrnUvUTWsFENEzDPbLJFEiAk6L" + sender := "AS48jKNQsDGkEdDvfwu1QpqjtqbCadrAq9nGXjFmdX3Z" eventExpected := &clienttypes.InboundEvent{ SenderChainID: chain.ChainId, Sender: sender, Receiver: sender, TxOrigin: sender, - Amount: 1280, - Memo: []byte("hello this is a good memo for you to enjoy"), + Amount: 100000, + Memo: []byte("0x7F8ae2ABb69A558CE6bAd546f25F0464D9e09e5B4955a3F38ff86ae92A914445099caa8eA2B9bA32"), BlockNumber: txResult.Slot, TxHash: txHash, Index: 0, // not a EVM smart contract call @@ -156,8 +156,8 @@ func Test_BuildInboundVoteMsgFromEvent(t *testing.T) { func Test_ParseInboundAsDeposit(t *testing.T) { // load archived inbound deposit tx result - // https://explorer.solana.com/tx/5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk?cluster=devnet - txHash := "5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk" + // https://explorer.solana.com/tx/MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j?cluster=devnet + txHash := "MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j" chain := chains.SolanaDevnet txResult := testutils.LoadSolanaInboundTxResult(t, TestDataDir, chain.ChainId, txHash, false) @@ -169,19 +169,19 @@ func Test_ParseInboundAsDeposit(t *testing.T) { // create observer chainParams := sample.ChainParams(chain.ChainId) - chainParams.GatewayAddress = GatewayAddressTest + chainParams.GatewayAddress = testutils.GatewayAddresses[chain.ChainId] ob, err := observer.NewObserver(chain, nil, *chainParams, nil, nil, database, base.DefaultLogger(), nil) require.NoError(t, err) // expected result - sender := "AKbG83jg2V65R7XvaPFrnUvUTWsFENEzDPbLJFEiAk6L" + sender := "AS48jKNQsDGkEdDvfwu1QpqjtqbCadrAq9nGXjFmdX3Z" eventExpected := &clienttypes.InboundEvent{ SenderChainID: chain.ChainId, Sender: sender, Receiver: sender, TxOrigin: sender, - Amount: 1280, - Memo: []byte("hello this is a good memo for you to enjoy"), + Amount: 100000, + Memo: []byte("0x7F8ae2ABb69A558CE6bAd546f25F0464D9e09e5B4955a3F38ff86ae92A914445099caa8eA2B9bA32"), BlockNumber: txResult.Slot, TxHash: txHash, Index: 0, // not a EVM smart contract call diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index b43043e30e..8c85c0e7cb 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -20,6 +20,9 @@ const ( // KeyringBackendFile is the file Cosmos keyring backend KeyringBackendFile KeyringBackend = "file" + + // DefaultRelayerKeyPath is the default path that relayer keys are stored + DefaultRelayerKeyPath = "~/.zetacored/relayer-keys" ) // ClientConfiguration is a subset of zetaclient config that is used by zetacore client @@ -163,6 +166,11 @@ func (c Config) GetKeyringBackend() KeyringBackend { func (c Config) GetRelayerKeyPath() string { c.mu.RLock() defer c.mu.RUnlock() + + // use default path if not configured + if c.RelayerKeyPath == "" { + return DefaultRelayerKeyPath + } return c.RelayerKeyPath } diff --git a/zetaclient/config/types_test.go b/zetaclient/config/types_test.go new file mode 100644 index 0000000000..6b83e19f17 --- /dev/null +++ b/zetaclient/config/types_test.go @@ -0,0 +1,16 @@ +package config_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/zetaclient/config" +) + +func Test_GetRelayerKeyPath(t *testing.T) { + // create config + cfg := config.New(false) + + // should return default relayer key path + require.Equal(t, config.DefaultRelayerKeyPath, cfg.GetRelayerKeyPath()) +} diff --git a/zetaclient/testdata/solana/chain_901_inbound_tx_result_5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk.json b/zetaclient/testdata/solana/chain_901_inbound_tx_result_5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk.json deleted file mode 100644 index 4e5b8bdb98..0000000000 --- a/zetaclient/testdata/solana/chain_901_inbound_tx_result_5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "slot": 309926562, - "blockTime": 1720328277, - "transaction": { - "signatures": [ - "5LuQMorgd11p8GWEw6pmyHCDtA26NUyeNFhLWPNk2oBoM9pkag1LzhwGSRos3j4TJLhKjswFhZkGtvSGdLDkmqsk" - ], - "message": { - "accountKeys": [ - "AKbG83jg2V65R7XvaPFrnUvUTWsFENEzDPbLJFEiAk6L", - "4pA5vqGeo4ipLoJzH3rdvguhifj1tCzoNM8vDRc4Xbmq", - "11111111111111111111111111111111", - "2kJndCL9NBR36ySiQ4bmArs4YgWQu67LmCDfLzk5Gb7s" - ], - "header": { - "numRequiredSignatures": 1, - "numReadonlySignedAccounts": 0, - "numReadonlyUnsignedAccounts": 2 - }, - "recentBlockhash": "9BYDuzjYhac5AqhsV3H3wNtj3tK1aT6k2oFLpTo1h3nL", - "instructions": [ - { - "programIdIndex": 3, - "accounts": [0, 1, 2, 3], - "data": "FQx87VJVvGQu6jGz7VmavZREFcSxTNNuB5hWd7npbi5M9CzWRjjcAaW9woj8WpxPcB9C9gmQYeYXTEsJ1mZ7W" - } - ] - } - }, - "meta": { - "err": null, - "fee": 5000, - "preBalances": [3171104080, 1447680, 1, 1141440], - "postBalances": [3171097800, 1448960, 1, 1141440], - "innerInstructions": [ - { - "index": 0, - "instructions": [ - { - "programIdIndex": 2, - "accounts": [0, 1], - "data": "3Bxs3zrrEsuzMyc3" - } - ] - } - ], - "preTokenBalances": [], - "postTokenBalances": [], - "logMessages": [ - "Program 2kJndCL9NBR36ySiQ4bmArs4YgWQu67LmCDfLzk5Gb7s invoke [1]", - "Program log: Instruction: Deposit", - "Program 11111111111111111111111111111111 invoke [2]", - "Program 11111111111111111111111111111111 success", - "Program log: AKbG83jg2V65R7XvaPFrnUvUTWsFENEzDPbLJFEiAk6L deposits 1280 lamports to PDA", - "Program 2kJndCL9NBR36ySiQ4bmArs4YgWQu67LmCDfLzk5Gb7s consumed 16968 of 200000 compute units", - "Program 2kJndCL9NBR36ySiQ4bmArs4YgWQu67LmCDfLzk5Gb7s success" - ], - "status": { "Ok": null }, - "rewards": [], - "loadedAddresses": { "readonly": [], "writable": [] }, - "computeUnitsConsumed": 16968 - }, - "version": 0 -} diff --git a/zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json b/zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json new file mode 100644 index 0000000000..210d639ead --- /dev/null +++ b/zetaclient/testdata/solana/chain_901_inbound_tx_result_MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j.json @@ -0,0 +1,64 @@ +{ + "slot": 321701608, + "blockTime": 1724732369, + "transaction": { + "signatures": [ + "MS3MPLN7hkbyCZFwKqXcg8fmEvQMD74fN6Ps2LSWXJoRxPW5ehaxBorK9q1JFVbqnAvu9jXm6ertj7kT7HpYw1j" + ], + "message": { + "accountKeys": [ + "AS48jKNQsDGkEdDvfwu1QpqjtqbCadrAq9nGXjFmdX3Z", + "2f9SLuUNb7TNeM6gzBwT4ZjbL5ZyKzzHg1Ce9yiquEjj", + "11111111111111111111111111111111", + "ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis" + ], + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 2 + }, + "recentBlockhash": "41txNvjedo2eu6aAofQfyLskAcgtrtgch9RpqnrKcv1a", + "instructions": [ + { + "programIdIndex": 3, + "accounts": [0, 1, 2], + "data": "4ALHYcAj3zFsNjmfeq7nDK1E8BsxRQRzhLjrqzmjYzL97Qkiz4rP1iQePmFAehfFEET7uczYLhhEVhtndBYNNm6ekHSkgsLzYDeSD2JSudHa6D5tqhVGjvXZ7qEouPiy9eptZfuYHE9X" + } + ] + } + }, + "meta": { + "err": null, + "fee": 5000, + "preBalances": [9999364000, 1001447680, 1, 1141440], + "postBalances": [9999259000, 1001547680, 1, 1141440], + "innerInstructions": [ + { + "index": 0, + "instructions": [ + { + "programIdIndex": 2, + "accounts": [0, 1], + "data": "3Bxs4ThwQbE4vyj5" + } + ] + } + ], + "preTokenBalances": [], + "postTokenBalances": [], + "logMessages": [ + "Program ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis invoke [1]", + "Program log: Instruction: Deposit", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program log: AS48jKNQsDGkEdDvfwu1QpqjtqbCadrAq9nGXjFmdX3Z deposits 100000 lamports to PDA", + "Program ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis consumed 17006 of 200000 compute units", + "Program ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis success" + ], + "status": { "Ok": null }, + "rewards": [], + "loadedAddresses": { "readonly": [], "writable": [] }, + "computeUnitsConsumed": 17006 + }, + "version": 0 +} diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index 3036035db4..a5af7f7d09 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -36,7 +36,7 @@ const ( // GatewayAddresses contains constants gateway addresses for testing var GatewayAddresses = map[int64]string{ // Gateway address on Solana devnet - chains.SolanaDevnet.ChainId: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d", + chains.SolanaDevnet.ChainId: "ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis", } // ConnectorAddresses contains constants ERC20 connector addresses for testing From 47a0609448e7eebdadd5bf18d5e22ff6a35dec15 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Fri, 30 Aug 2024 13:31:49 +0200 Subject: [PATCH 2/2] test: add E2E testing for V2 contract migration (#2763) * rename migration into tss migration * rename post tss migration * initialize migration function body * update contract version used * migration test * remove migration as test * add migration option * generate * make some fixes * make generate * fix gateway address * fixes * add some fixes * Update contrib/localnet/orchestrator/start-zetae2e.sh Co-authored-by: skosito * comments * changelog * fix config file * Update cmd/zetae2e/local/tss_migration.go Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> * Update e2e/txserver/authority.go Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> * refactor bitcoin error * fix approval functions --------- Co-authored-by: skosito Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- .github/workflows/e2e.yml | 8 + Makefile | 10 + changelog.md | 1 + cmd/zetae2e/config/localnet.yml | 16 ++ cmd/zetae2e/local/local.go | 74 ++---- cmd/zetae2e/local/migration.go | 63 ------ cmd/zetae2e/local/tss_migration.go | 55 ++++- cmd/zetae2e/local/v2.go | 62 ++++- .../localnet/orchestrator/start-zetae2e.sh | 20 ++ contrib/localnet/scripts/start-zetacored.sh | 12 + e2e/config/config.go | 44 +++- .../test_migrate_erc20_custody_funds.go | 3 - e2e/runner/accounting.go | 15 +- e2e/runner/evm.go | 14 +- e2e/runner/v2_migration.go | 212 ++++++++++++++++++ e2e/runner/v2_setup_evm.go | 7 +- e2e/runner/v2_setup_zeta.go | 7 +- e2e/txserver/authority.go | 33 +++ e2e/txserver/zeta_tx_server.go | 1 + go.mod | 4 +- go.sum | 4 +- .../msg_server_update_gateway_contract.go | 34 ++- ...msg_server_update_gateway_contract_test.go | 128 ++++++++--- x/fungible/keeper/v2_evm.go | 34 ++- zetaclient/chains/bitcoin/errors.go | 6 + zetaclient/chains/bitcoin/observer/inbound.go | 29 ++- .../chains/bitcoin/observer/observer.go | 11 +- zetaclient/chains/evm/signer/signer.go | 7 +- zetaclient/orchestrator/orchestrator.go | 34 +-- 29 files changed, 732 insertions(+), 216 deletions(-) delete mode 100644 cmd/zetae2e/local/migration.go create mode 100644 e2e/runner/v2_migration.go create mode 100644 e2e/txserver/authority.go create mode 100644 zetaclient/chains/bitcoin/errors.go diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3cbbc56594..eaf28ec983 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -38,6 +38,7 @@ jobs: TSS_MIGRATION_TESTS: ${{ steps.matrix-conditionals.outputs.TSS_MIGRATION_TESTS }} SOLANA_TESTS: ${{ steps.matrix-conditionals.outputs.SOLANA_TESTS }} V2_TESTS: ${{ steps.matrix-conditionals.outputs.V2_TESTS }} + V2_MIGRATION_TESTS: ${{ steps.matrix-conditionals.outputs.V2_MIGRATION_TESTS }} steps: # use api rather than event context to avoid race conditions (label added after push) - id: matrix-conditionals @@ -62,6 +63,7 @@ jobs: core.setOutput('TSS_MIGRATION_TESTS', labels.includes('TSS_MIGRATION_TESTS')); core.setOutput('SOLANA_TESTS', labels.includes('SOLANA_TESTS')); core.setOutput('V2_TESTS', labels.includes('V2_TESTS')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) + core.setOutput('V2_MIGRATION_TESTS', labels.includes('V2_MIGRATION_TESTS')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } else if (context.eventName === 'merge_group') { core.setOutput('DEFAULT_TESTS', true); core.setOutput('UPGRADE_LIGHT_TESTS', true); @@ -76,6 +78,7 @@ jobs: core.setOutput('PERFORMANCE_TESTS', true); core.setOutput('STATEFUL_DATA_TESTS', true); core.setOutput('V2_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) + core.setOutput('V2_MIGRATION_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } else if (context.eventName === 'schedule') { core.setOutput('DEFAULT_TESTS', true); core.setOutput('UPGRADE_TESTS', true); @@ -87,6 +90,7 @@ jobs: core.setOutput('TSS_MIGRATION_TESTS', true); core.setOutput('SOLANA_TESTS', true); core.setOutput('V2_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) + core.setOutput('V2_MIGRATION_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } else if (context.eventName === 'workflow_dispatch') { const makeTargets = context.payload.inputs['make-targets'].split(','); core.setOutput('DEFAULT_TESTS', makeTargets.includes('default-test')); @@ -99,6 +103,7 @@ jobs: core.setOutput('TSS_MIGRATION_TESTS', makeTargets.includes('tss-migration-test')); core.setOutput('SOLANA_TESTS', makeTargets.includes('solana-test')); core.setOutput('V2_TESTS', makeTargets.includes('v2-test')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) + core.setOutput('V2_MIGRATION_TESTS', makeTargets.includes('v2-migration-test')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } e2e: @@ -140,6 +145,9 @@ jobs: - make-target: "start-v2-test" runs-on: ubuntu-20.04 run: ${{ needs.matrix-conditionals.outputs.V2_TESTS == 'true' }} + - make-target: "start-upgrade-v2-migration-test" + runs-on: ubuntu-20.04 + run: ${{ needs.matrix-conditionals.outputs.V2_MIGRATION_TESTS == 'true' }} name: ${{ matrix.make-target }} uses: ./.github/workflows/reusable-e2e.yml with: diff --git a/Makefile b/Makefile index aa4fcff5d0..5a3d683da2 100644 --- a/Makefile +++ b/Makefile @@ -321,6 +321,16 @@ start-upgrade-test-admin: zetanode-upgrade export E2E_ARGS="--skip-regular --test-admin" && \ cd contrib/localnet/ && $(DOCKER_COMPOSE) --profile upgrade -f docker-compose.yml -f docker-compose-upgrade.yml up -d +# this test upgrades from v18 and execute the v2 contracts migration process +# this tests is part of upgrade test part because it should run the upgrade from v18 to fully replicate the upgrade process +start-upgrade-v2-migration-test: zetanode-upgrade + @echo "--> Starting v2 migration upgrade test" + export LOCALNET_MODE=upgrade && \ + export UPGRADE_HEIGHT=90 && \ + export E2E_ARGS="--test-v2-migration" && \ + cd contrib/localnet/ && $(DOCKER_COMPOSE) --profile upgrade -f docker-compose.yml -f docker-compose-upgrade.yml up -d + + start-upgrade-import-mainnet-test: zetanode-upgrade @echo "--> Starting import-data upgrade test" export LOCALNET_MODE=upgrade && \ diff --git a/changelog.md b/changelog.md index 56e002b735..31fe5550fc 100644 --- a/changelog.md +++ b/changelog.md @@ -28,6 +28,7 @@ * [2661](https://github.com/zeta-chain/node/pull/2661) - update connector and erc20Custody addresses in tss migration e2e tests * [2726](https://github.com/zeta-chain/node/pull/2726) - add e2e tests for deposit and call, deposit and revert * [2703](https://github.com/zeta-chain/node/pull/2703) - add e2e tests for stateful precompiled contracts +* [2763](https://github.com/zeta-chain/node/pull/2763) - add V2 contracts migration test ### Fixes diff --git a/cmd/zetae2e/config/localnet.yml b/cmd/zetae2e/config/localnet.yml index 7f7e8c6c73..48791750e0 100644 --- a/cmd/zetae2e/config/localnet.yml +++ b/cmd/zetae2e/config/localnet.yml @@ -45,6 +45,22 @@ additional_accounts: bech32_address: "zeta1k4f0l2e9qqjccxnstwj0uaarxvn44lj990she9" evm_address: "0xb552FFAb2500258C1A705Ba4Fe77A333275AFE45" private_key: "bd6b74387f11b31d21e87c2ae7a23ec269aee08a355dad6c508a6fceb79d1f48" + user_v2_ether: + bech32_address: "zeta1erlqlpl5da7a9r3emzw60kax9fxc3h0r3z7c5e" + evm_address: "0xC8fe0F87f46F7Dd28e39D89Da7Dba62A4D88dde3" + private_key: "11c25af71c82602a681ce622bf76f4f0fbc3b7f23ce935db6249d1517322f436" + user_v2_erc20: + bech32_address: "zeta12wp6syndml6jd32m7f9mn2wscsxz6cff8nczl4" + evm_address: "0x5383A8126ddff526C55bF24Bb9a9D0c40c2d6129" + private_key: "77b0e4dcc29c5c47b6999dabd42abcfdf7750ccc86d6659c1373ec1ea3b4af6c" + user_v2_ether_revert: + bech32_address: "zeta1m7m5xd79x9qmlyfpqxcwuac04r3dewfpdcfw5e" + evm_address: "0xdFb74337c53141bf912101b0Ee770FA8e2DCB921" + private_key: "be7098604cc40f95d68298a3b4ae13972ac8a3df271ba19ddf169070d30e8ba8" + user_v2_erc20_revert: + bech32_address: "zeta1nry9yeg6njhjrp2ctppa8558vqxal9fxk69zxg" + evm_address: "0x98c852651A9CAF2185585843d3D287600Ddf9526" + private_key: "bf9456c679bb5a952a9a137fcfc920e0413efdb97c36de1e57455763084230cb" policy_accounts: emergency_policy_account: bech32_address: "zeta16m2cnrdwtgweq4njc6t470vl325gw4kp6s7tap" diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index c8d92fce28..4a0d62aae0 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -41,6 +41,7 @@ const ( flagSkipBitcoinSetup = "skip-bitcoin-setup" flagSkipHeaderProof = "skip-header-proof" flagTestV2 = "test-v2" + flagTestV2Migration = "test-v2-migration" flagSkipTrackerCheck = "skip-tracker-check" flagSkipPrecompiles = "skip-precompiles" ) @@ -76,12 +77,15 @@ func NewLocalCmd() *cobra.Command { cmd.Flags().Bool(flagSkipHeaderProof, false, "set to true to skip header proof tests") cmd.Flags().Bool(flagTestTSSMigration, false, "set to true to include a migration test at the end") cmd.Flags().Bool(flagTestV2, false, "set to true to run tests for v2 contracts") + cmd.Flags().Bool(flagTestV2Migration, false, "set to true to run tests for v2 contracts migration test") cmd.Flags().Bool(flagSkipTrackerCheck, false, "set to true to skip tracker check at the end of the tests") cmd.Flags().Bool(flagSkipPrecompiles, false, "set to true to skip stateful precompiled contracts test") return cmd } +// TODO: simplify this file: put the different type of tests in separate files +// https://github.com/zeta-chain/node/issues/2762 func localE2ETest(cmd *cobra.Command, _ []string) { // fetch flags var ( @@ -102,6 +106,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) testV2 = must(cmd.Flags().GetBool(flagTestV2)) + testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) ) @@ -235,6 +240,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { os.Exit(0) } + // run the v2 migration + if testV2Migration { + deployerRunner.RunV2Migration() + } + // run tests var eg errgroup.Group @@ -338,7 +348,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { // 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 - // TODO: renenable this test as per the issue below + // TODO: reenable this test as per the issue below // https://github.com/zeta-chain/node/issues/1980 // e2etests.TestMigrateChainSupportName, )) @@ -363,65 +373,15 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...)) } + if testV2 { // update the ERC20 custody contract for v2 tests - deployerRunner.UpdateChainParamsERC20CustodyContract() - - //// Test happy paths for gas token workflow - eg.Go(v2TestRoutine(conf, "eth", conf.AdditionalAccounts.UserEther, color.FgHiGreen, deployerRunner, verbose, - e2etests.TestV2ETHDepositName, - e2etests.TestV2ETHDepositAndCallName, - e2etests.TestV2ETHWithdrawName, - e2etests.TestV2ETHWithdrawAndCallName, - e2etests.TestV2ZEVMToEVMCallName, - e2etests.TestV2EVMToZEVMCallName, - )) - - //// Test happy paths for erc20 token workflow - eg.Go(v2TestRoutine(conf, "erc20", conf.AdditionalAccounts.UserERC20, color.FgHiBlue, deployerRunner, verbose, - e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM - e2etests.TestV2ERC20DepositName, - e2etests.TestV2ERC20DepositAndCallName, - e2etests.TestV2ERC20WithdrawName, - e2etests.TestV2ERC20WithdrawAndCallName, - )) + // note: not run in testV2Migration because it is already run in the migration process + deployerRunner.UpdateChainParamsV2Contracts() + } - // Test revert cases for gas token workflow - eg.Go( - v2TestRoutine( - conf, - "eth-revert", - conf.AdditionalAccounts.UserZetaTest, - color.FgHiYellow, - deployerRunner, - verbose, - e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM and withdraw - e2etests.TestV2ETHDepositAndCallRevertName, - e2etests.TestV2ETHDepositAndCallRevertWithCallName, - e2etests.TestV2ETHWithdrawAndCallRevertName, - e2etests.TestV2ETHWithdrawAndCallRevertWithCallName, - ), - ) - - // Test revert cases for erc20 token workflow - eg.Go( - v2TestRoutine( - conf, - "erc20-revert", - conf.AdditionalAccounts.UserBitcoin, - color.FgHiRed, - deployerRunner, - verbose, - e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM - e2etests.TestV2ERC20DepositName, // necessary to have assets to withdraw - e2etests.TestOperationAddLiquidityETHName, // liquidity with gas and ERC20 are necessary for reverts - e2etests.TestOperationAddLiquidityERC20Name, - e2etests.TestV2ERC20DepositAndCallRevertName, - e2etests.TestV2ERC20DepositAndCallRevertWithCallName, - e2etests.TestV2ERC20WithdrawAndCallRevertName, - e2etests.TestV2ERC20WithdrawAndCallRevertWithCallName, - ), - ) + if testV2 || testV2Migration { + startV2Tests(&eg, conf, deployerRunner, verbose) } // while tests are executed, monitor blocks in parallel to check if system txs are on top and they have biggest priority diff --git a/cmd/zetae2e/local/migration.go b/cmd/zetae2e/local/migration.go deleted file mode 100644 index 27d9682990..0000000000 --- a/cmd/zetae2e/local/migration.go +++ /dev/null @@ -1,63 +0,0 @@ -package local - -import ( - "fmt" - "time" - - "github.com/fatih/color" - - "github.com/zeta-chain/zetacore/e2e/config" - "github.com/zeta-chain/zetacore/e2e/e2etests" - "github.com/zeta-chain/zetacore/e2e/runner" -) - -// migrationRoutine runs migration related e2e tests -func migrationRoutine( - conf config.Config, - deployerRunner *runner.E2ERunner, - verbose bool, - testNames ...string, -) func() error { - return func() (err error) { - account := conf.AdditionalAccounts.UserMigration - // initialize runner for migration test - migrationTestRunner, err := initTestRunner( - "migration", - conf, - deployerRunner, - account, - runner.NewLogger(verbose, color.FgHiGreen, "migration"), - runner.WithZetaTxServer(deployerRunner.ZetaTxServer), - ) - if err != nil { - return err - } - - migrationTestRunner.Logger.Print("🏃 starting migration tests") - startTime := time.Now() - - if len(testNames) == 0 { - migrationTestRunner.Logger.Print("🍾 migration tests completed in %s", time.Since(startTime).String()) - return nil - } - // run migration test - testsToRun, err := migrationTestRunner.GetE2ETestsToRunByName( - e2etests.AllE2ETests, - testNames..., - ) - if err != nil { - return fmt.Errorf("migration tests failed: %v", err) - } - - if err := migrationTestRunner.RunE2ETests(testsToRun); err != nil { - return fmt.Errorf("migration tests failed: %v", err) - } - if err := migrationTestRunner.CheckBtcTSSBalance(); err != nil { - return err - } - - migrationTestRunner.Logger.Print("🍾 migration tests completed in %s", time.Since(startTime).String()) - - return err - } -} diff --git a/cmd/zetae2e/local/tss_migration.go b/cmd/zetae2e/local/tss_migration.go index a7acf80daa..860cebf91a 100644 --- a/cmd/zetae2e/local/tss_migration.go +++ b/cmd/zetae2e/local/tss_migration.go @@ -1,9 +1,11 @@ package local import ( + "fmt" "os" "time" + "github.com/fatih/color" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/config" @@ -12,6 +14,57 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) +// tssMigrationTestRoutine runs TSS migration related e2e tests +func tssMigrationTestRoutine( + conf config.Config, + deployerRunner *runner.E2ERunner, + verbose bool, + testNames ...string, +) func() error { + return func() (err error) { + account := conf.AdditionalAccounts.UserMigration + // initialize runner for migration test + tssMigrationTestRunner, err := initTestRunner( + "tssMigration", + conf, + deployerRunner, + account, + runner.NewLogger(verbose, color.FgHiGreen, "migration"), + runner.WithZetaTxServer(deployerRunner.ZetaTxServer), + ) + if err != nil { + return err + } + + tssMigrationTestRunner.Logger.Print("🏃 starting TSS migration tests") + startTime := time.Now() + + if len(testNames) == 0 { + tssMigrationTestRunner.Logger.Print("🍾 TSS migration tests completed in %s", time.Since(startTime).String()) + return nil + } + // run TSS migration test + testsToRun, err := tssMigrationTestRunner.GetE2ETestsToRunByName( + e2etests.AllE2ETests, + testNames..., + ) + if err != nil { + return fmt.Errorf("TSS migration tests failed: %v", err) + } + + if err := tssMigrationTestRunner.RunE2ETests(testsToRun); err != nil { + return fmt.Errorf("TSS migration tests failed: %v", err) + } + if err := tssMigrationTestRunner.CheckBtcTSSBalance(); err != nil { + return err + } + + tssMigrationTestRunner.Logger.Print("🍾 TSS migration tests completed in %s", time.Since(startTime).String()) + + return nil + } +} + func TSSMigration(deployerRunner *runner.E2ERunner, logger *runner.Logger, verbose bool, conf config.Config) { migrationStartTime := time.Now() logger.Print("🏁 starting tss migration") @@ -30,7 +83,7 @@ func TSSMigration(deployerRunner *runner.E2ERunner, logger *runner.Logger, verbo // Run migration // migrationRoutine runs migration e2e test , which migrates funds from the older TSS to the new one // The zetaclient restarts required for this process are managed by the background workers in zetaclient (TSSListener) - fn := migrationRoutine(conf, deployerRunner, verbose, e2etests.TestMigrateTSSName) + fn := tssMigrationTestRoutine(conf, deployerRunner, verbose, e2etests.TestMigrateTSSName) if err := fn(); err != nil { logger.Print("❌ %v", err) diff --git a/cmd/zetae2e/local/v2.go b/cmd/zetae2e/local/v2.go index e87785de31..b38be32868 100644 --- a/cmd/zetae2e/local/v2.go +++ b/cmd/zetae2e/local/v2.go @@ -5,13 +5,73 @@ import ( "time" "github.com/fatih/color" + "golang.org/x/sync/errgroup" "github.com/zeta-chain/zetacore/e2e/config" "github.com/zeta-chain/zetacore/e2e/e2etests" "github.com/zeta-chain/zetacore/e2e/runner" ) -// erc20TestRoutine runs v2 related e2e tests +// startV2Tests starts v2 related tests in parallel +func startV2Tests(eg *errgroup.Group, conf config.Config, deployerRunner *runner.E2ERunner, verbose bool) { + // Test happy paths for gas token workflow + eg.Go(v2TestRoutine(conf, "eth", conf.AdditionalAccounts.UserV2Ether, color.FgHiGreen, deployerRunner, verbose, + e2etests.TestV2ETHDepositName, + e2etests.TestV2ETHDepositAndCallName, + e2etests.TestV2ETHWithdrawName, + e2etests.TestV2ETHWithdrawAndCallName, + e2etests.TestV2ZEVMToEVMCallName, + e2etests.TestV2EVMToZEVMCallName, + )) + + // Test happy paths for erc20 token workflow + eg.Go(v2TestRoutine(conf, "erc20", conf.AdditionalAccounts.UserV2ERC20, color.FgHiBlue, deployerRunner, verbose, + e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM + e2etests.TestV2ERC20DepositName, + e2etests.TestV2ERC20DepositAndCallName, + e2etests.TestV2ERC20WithdrawName, + e2etests.TestV2ERC20WithdrawAndCallName, + )) + + // Test revert cases for gas token workflow + eg.Go( + v2TestRoutine( + conf, + "eth-revert", + conf.AdditionalAccounts.UserV2EtherRevert, + color.FgHiYellow, + deployerRunner, + verbose, + e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM and withdraw + e2etests.TestV2ETHDepositAndCallRevertName, + e2etests.TestV2ETHDepositAndCallRevertWithCallName, + e2etests.TestV2ETHWithdrawAndCallRevertName, + e2etests.TestV2ETHWithdrawAndCallRevertWithCallName, + ), + ) + + // Test revert cases for erc20 token workflow + eg.Go( + v2TestRoutine( + conf, + "erc20-revert", + conf.AdditionalAccounts.UserV2ERC20Revert, + color.FgHiRed, + deployerRunner, + verbose, + e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM + e2etests.TestV2ERC20DepositName, // necessary to have assets to withdraw + e2etests.TestOperationAddLiquidityETHName, // liquidity with gas and ERC20 are necessary for reverts + e2etests.TestOperationAddLiquidityERC20Name, + e2etests.TestV2ERC20DepositAndCallRevertName, + e2etests.TestV2ERC20DepositAndCallRevertWithCallName, + e2etests.TestV2ERC20WithdrawAndCallRevertName, + e2etests.TestV2ERC20WithdrawAndCallRevertWithCallName, + ), + ) +} + +// v2TestRoutine runs v2 related e2e tests // TODO: this routine will be broken down in the future and will replace most current tests // we keep a single routine for v2 for simplicity // https://github.com/zeta-chain/node/issues/2554 diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index 98ba9060e7..2dfdb123ba 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -91,6 +91,26 @@ address=$(yq -r '.additional_accounts.user_migration.evm_address' config.yml) echo "funding migration tester address ${address} with 10000 Ether" geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null +# unlock v2 ethers tests accounts +address=$(yq -r '.additional_accounts.user_v2_ether.evm_address' config.yml) +echo "funding v2 ethers tester address ${address} with 10000 Ether" +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null + +# unlock v2 erc20 tests accounts +address=$(yq -r '.additional_accounts.user_v2_erc20.evm_address' config.yml) +echo "funding v2 erc20 tester address ${address} with 10000 Ether" +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null + +# unlock v2 ethers revert tests accounts +address=$(yq -r '.additional_accounts.user_v2_ether_revert.evm_address' config.yml) +echo "funding v2 ethers revert tester address ${address} with 10000 Ether" +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null + +# unlock v2 erc20 revert tests accounts +address=$(yq -r '.additional_accounts.user_v2_erc20_revert.evm_address' config.yml) +echo "funding v2 erc20 revert tester address ${address} with 10000 Ether" +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null + # unlock local solana relayer accounts if host solana > /dev/null; then solana_url=$(yq -r '.rpcs.solana' config.yml) diff --git a/contrib/localnet/scripts/start-zetacored.sh b/contrib/localnet/scripts/start-zetacored.sh index 14980d195f..f1d3e11872 100755 --- a/contrib/localnet/scripts/start-zetacored.sh +++ b/contrib/localnet/scripts/start-zetacored.sh @@ -254,6 +254,18 @@ then # migration tester address=$(yq -r '.additional_accounts.user_migration.bech32_address' /root/config.yml) zetacored add-genesis-account "$address" 100000000000000000000000000azeta +# v2 ether tester + address=$(yq -r '.additional_accounts.user_v2_ether.bech32_address' /root/config.yml) + zetacored add-genesis-account "$address" 100000000000000000000000000azeta +# v2 erc20 tester + address=$(yq -r '.additional_accounts.user_v2_erc20.bech32_address' /root/config.yml) + zetacored add-genesis-account "$address" 100000000000000000000000000azeta +# v2 ether revert tester + address=$(yq -r '.additional_accounts.user_v2_ether_revert.bech32_address' /root/config.yml) + zetacored add-genesis-account "$address" 100000000000000000000000000azeta +# v2 erc20 revert tester + address=$(yq -r '.additional_accounts.user_v2_erc20_revert.bech32_address' /root/config.yml) + zetacored add-genesis-account "$address" 100000000000000000000000000azeta # 3. Copy the genesis.json to all the nodes .And use it to create a gentx for every node zetacored gentx operator 1000000000000000000000azeta --chain-id=$CHAINID --keyring-backend=$KEYRING --gas-prices 20000000000azeta diff --git a/e2e/config/config.go b/e2e/config/config.go index 699a616c3c..15a7d57306 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -61,16 +61,20 @@ type Account struct { // AdditionalAccounts are extra accounts required to run specific tests type AdditionalAccounts struct { - UserERC20 Account `yaml:"user_erc20"` - UserZetaTest Account `yaml:"user_zeta_test"` - UserZEVMMPTest Account `yaml:"user_zevm_mp_test"` - UserBitcoin Account `yaml:"user_bitcoin"` - UserSolana Account `yaml:"user_solana"` - UserEther Account `yaml:"user_ether"` - UserMisc Account `yaml:"user_misc"` - UserAdmin Account `yaml:"user_admin"` - UserMigration Account `yaml:"user_migration"` - UserPrecompile Account `yaml:"user_precompile"` + UserERC20 Account `yaml:"user_erc20"` + UserZetaTest Account `yaml:"user_zeta_test"` + UserZEVMMPTest Account `yaml:"user_zevm_mp_test"` + UserBitcoin Account `yaml:"user_bitcoin"` + UserSolana Account `yaml:"user_solana"` + UserEther Account `yaml:"user_ether"` + UserMisc Account `yaml:"user_misc"` + UserAdmin Account `yaml:"user_admin"` + UserMigration Account `yaml:"user_migration"` // used for TSS migration, TODO: rename (https://github.com/zeta-chain/node/issues/2780) + UserPrecompile Account `yaml:"user_precompile"` + UserV2Ether Account `yaml:"user_v2_ether"` + UserV2ERC20 Account `yaml:"user_v2_erc20"` + UserV2EtherRevert Account `yaml:"user_v2_ether_revert"` + UserV2ERC20Revert Account `yaml:"user_v2_erc20_revert"` } type PolicyAccounts struct { @@ -227,6 +231,10 @@ func (a AdditionalAccounts) AsSlice() []Account { a.UserAdmin, a.UserMigration, a.UserPrecompile, + a.UserV2Ether, + a.UserV2ERC20, + a.UserV2EtherRevert, + a.UserV2ERC20Revert, } } @@ -323,6 +331,22 @@ func (c *Config) GenerateKeys() error { if err != nil { return err } + c.AdditionalAccounts.UserV2Ether, err = generateAccount() + if err != nil { + return err + } + c.AdditionalAccounts.UserV2ERC20, err = generateAccount() + if err != nil { + return err + } + c.AdditionalAccounts.UserV2EtherRevert, err = generateAccount() + if err != nil { + return err + } + c.AdditionalAccounts.UserV2ERC20Revert, err = generateAccount() + if err != nil { + return err + } c.PolicyAccounts.EmergencyPolicyAccount, err = generateAccount() if err != nil { diff --git a/e2e/e2etests/test_migrate_erc20_custody_funds.go b/e2e/e2etests/test_migrate_erc20_custody_funds.go index 1c38909b3d..f26cd9e503 100644 --- a/e2e/e2etests/test_migrate_erc20_custody_funds.go +++ b/e2e/e2etests/test_migrate_erc20_custody_funds.go @@ -25,9 +25,6 @@ func TestMigrateERC20CustodyFunds(r *runner.E2ERunner, _ []string) { 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(), diff --git a/e2e/runner/accounting.go b/e2e/runner/accounting.go index 7c9a0c5746..c56e9a76b7 100644 --- a/e2e/runner/accounting.go +++ b/e2e/runner/accounting.go @@ -170,11 +170,18 @@ func (r *E2ERunner) checkERC20TSSBalance() error { if err != nil { return err } - custodyV2Balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyV2Addr) - if err != nil { - return err + + custodyFullBalance := custodyBalance + + // take into account the balance of the new ERC20 custody contract as v2 test use this contract + // if both addresses are equal, then there is no need to check the balance of the new contract + if r.ERC20CustodyAddr.Hex() != r.ERC20CustodyV2Addr.Hex() { + custodyV2Balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyV2Addr) + if err != nil { + return err + } + custodyFullBalance = big.NewInt(0).Add(custodyBalance, custodyV2Balance) } - custodyFullBalance := big.NewInt(0).Add(custodyBalance, custodyV2Balance) erc20zrc20Supply, err := r.ERC20ZRC20.TotalSupply(&bind.CallOpts{}) if err != nil { diff --git a/e2e/runner/evm.go b/e2e/runner/evm.go index 3b5e7f8d90..9bb1bb9082 100644 --- a/e2e/runner/evm.go +++ b/e2e/runner/evm.go @@ -185,11 +185,12 @@ func (r *E2ERunner) ApproveERC20OnEVM(allowed ethcommon.Address) { // check if allowance is zero before calling this method // allow a high amount to avoid multiple approvals func (r *E2ERunner) ApproveETHZRC20(allowed ethcommon.Address) { - allowance, err := r.ETHZRC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), r.GatewayEVMAddr) + allowance, err := r.ETHZRC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), allowed) require.NoError(r, err) - // approve 1M*1e18 if allowance is zero - if allowance.Cmp(big.NewInt(0)) == 0 { + // approve 1M*1e18 if allowance is below 1k + thousand := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000)) + if allowance.Cmp(thousand) < 0 { tx, err := r.ETHZRC20.Approve(r.ZEVMAuth, allowed, big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000000))) require.NoError(r, err) receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) @@ -201,11 +202,12 @@ func (r *E2ERunner) ApproveETHZRC20(allowed ethcommon.Address) { // check if allowance is zero before calling this method // allow a high amount to avoid multiple approvals func (r *E2ERunner) ApproveERC20ZRC20(allowed ethcommon.Address) { - allowance, err := r.ERC20ZRC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), r.GatewayEVMAddr) + allowance, err := r.ERC20ZRC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), allowed) require.NoError(r, err) - // approve 1M*1e18 if allowance is zero - if allowance.Cmp(big.NewInt(0)) == 0 { + // approve 1M*1e18 if allowance is below 1k + thousand := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000)) + if allowance.Cmp(thousand) < 0 { tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, allowed, big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000000))) require.NoError(r, err) receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) diff --git a/e2e/runner/v2_migration.go b/e2e/runner/v2_migration.go new file mode 100644 index 0000000000..abfb114409 --- /dev/null +++ b/e2e/runner/v2_migration.go @@ -0,0 +1,212 @@ +package runner + +import ( + "math/big" + + sdkmath "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" + + "github.com/zeta-chain/zetacore/e2e/txserver" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/pkg/coin" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" +) + +// RunV2Migration runs the process for the v2 migration +func (r *E2ERunner) RunV2Migration() { + // prepare for v2 migration: deposit erc20 to ensure that the custody contract has funds to migrate + oneThousand := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000)) + erc20Deposit := r.DepositERC20WithAmountAndMessage( + r.EVMAddress(), + oneThousand, + []byte{}, + ) + r.WaitForMinedCCTX(erc20Deposit) + + // Part 1: add new admin authorization + r.Logger.Info("Part 1: Adding authorization for new v2 contracts") + err := r.ZetaTxServer.AddAuthorization("/zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatus") + require.NoError(r, err) + + err = r.ZetaTxServer.AddAuthorization("/zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds") + require.NoError(r, err) + + err = r.ZetaTxServer.AddAuthorization("/zetachain.zetacore.fungible.MsgUpdateGatewayContract") + require.NoError(r, err) + + // Part 2: deploy v2 contracts on EVM chain + r.Logger.Info("Part 2: Deploying v2 contracts on EVM chain") + r.SetupEVMV2() + + // Part 3: upgrade all ZRC20s + r.Logger.Info("Part 3: Upgrading ZRC20s") + r.upgradeZRC20s() + + // Part 4: deploy gateway on ZetaChain + r.Logger.Info("Part 4: Deploying Gateway ZEVM") + r.SetZEVMContractsV2() + + // Part 5: migrate ERC20 custody funds + r.Logger.Info("Part 5: Migrating ERC20 custody funds") + r.migrateERC20CustodyFunds() +} + +// upgradeZRC20s upgrades all ZRC20s to the new version +func (r *E2ERunner) upgradeZRC20s() { + // get chain IDs + evmChainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + btcChainID := r.GetBitcoinChainID() + + // upgrade ETH ZRC20 + r.Logger.Info("Upgrading ETH ZRC20") + r.upgradeZRC20(r.ETHZRC20Addr, r.ETHZRC20, evmChainID, uint8(coin.CoinType_Gas)) + + // upgrade ERC20 ZRC20 + r.Logger.Info("Upgrading ERC20 ZRC20") + r.upgradeZRC20(r.ERC20ZRC20Addr, r.ERC20ZRC20, evmChainID, uint8(coin.CoinType_ERC20)) + + // upgrade BTC ZRC20 + r.Logger.Info("Upgrading BTC ZRC20") + r.upgradeZRC20(r.BTCZRC20Addr, r.BTCZRC20, big.NewInt(btcChainID), uint8(coin.CoinType_Gas)) +} + +// zrc20Caller is an interface to call ZRC20 functions +type zrc20Caller interface { + Name(opts *bind.CallOpts) (string, error) + Symbol(opts *bind.CallOpts) (string, error) + Decimals(opts *bind.CallOpts) (uint8, error) +} + +// upgradeZRC20 upgrades a ZRC20 to the new version +func (r *E2ERunner) upgradeZRC20( + zrc20Addr common.Address, + zrc20Caller zrc20Caller, + chainID *big.Int, + coinType uint8, +) { + // deploy new ZRC20 version + name, err := zrc20Caller.Name(&bind.CallOpts{}) + require.NoError(r, err) + symbol, err := zrc20Caller.Symbol(&bind.CallOpts{}) + require.NoError(r, err) + decimal, err := zrc20Caller.Decimals(&bind.CallOpts{}) + require.NoError(r, err) + + newZRC20Addr, newZRC20Tx, _, err := zrc20.DeployZRC20( + r.ZEVMAuth, + r.ZEVMClient, + name, + symbol, + decimal, + chainID, + coinType, + big.NewInt(100_000), + r.SystemContractAddr, + r.SystemContractAddr, // gateway is not deployed yet, gateway will be set during MsgUpdateGatewayContract phase by the protocol + ) + require.NoError(r, err) + + // wait tx to be mined + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, newZRC20Tx, r.Logger, r.ReceiptTimeout) + require.EqualValues(r, ethtypes.ReceiptStatusSuccessful, receipt.Status) + + // upgrade ZRC20 bytecode with the one of the new ZRC20 + codeHashRes, err := r.FungibleClient.CodeHash(r.Ctx, &fungibletypes.QueryCodeHashRequest{ + Address: newZRC20Addr.String(), + }) + require.NoError(r, err) + + msg := fungibletypes.NewMsgUpdateContractBytecode( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + zrc20Addr.Hex(), + codeHashRes.CodeHash, + ) + _, err = r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msg) + require.NoError(r, err) +} + +func (r *E2ERunner) migrateERC20CustodyFunds() { + evmChainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + + // Part 1: pause the ERC20Custody v1 + r.Logger.Info("Pausing ERC20 custody v1 contract") + msgPausing := crosschaintypes.NewMsgUpdateERC20CustodyPauseStatus( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + evmChainID.Int64(), + true, + ) + res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msgPausing) + 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, "pausing") + + // wait for the cctx to be mined + r.WaitForMinedCCTXFromIndex(cctxIndex) + + // Part 2: pause the ZRC20 ERC20 + msgPause := fungibletypes.NewMsgPauseZRC20( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.EmergencyPolicyName), + []string{r.ERC20ZRC20Addr.Hex()}, + ) + _, err = r.ZetaTxServer.BroadcastTx(utils.EmergencyPolicyName, msgPause) + require.NoError(r, err) + + // Part 3: migrate all funds of the ERC20 + balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyAddr) + require.NoError(r, err) + + // ensure balance is not zero to ensure the test tests actual migration + require.NotEqual(r, int64(0), balance.Int64()) + + // send MigrateERC20CustodyFunds command + msgMigration := crosschaintypes.NewMsgMigrateERC20CustodyFunds( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + evmChainID.Int64(), + r.ERC20CustodyV2Addr.Hex(), + r.ERC20Addr.Hex(), + sdkmath.NewUintFromBigInt(balance), + ) + res, err = r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msgMigration) + 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) + + // Part 4: unpause the ZRC20 + msgUnpause := fungibletypes.NewMsgUnpauseZRC20( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.OperationalPolicyName), + []string{r.ERC20ZRC20Addr.Hex()}, + ) + _, err = r.ZetaTxServer.BroadcastTx(utils.OperationalPolicyName, msgUnpause) + require.NoError(r, err) + + // Part 5: update the ERC20 custody contract in the chain params and in the runner + r.UpdateChainParamsV2Contracts() + + r.ERC20CustodyAddr = r.ERC20CustodyV2Addr +} diff --git a/e2e/runner/v2_setup_evm.go b/e2e/runner/v2_setup_evm.go index 001da12e04..cbacc59550 100644 --- a/e2e/runner/v2_setup_evm.go +++ b/e2e/runner/v2_setup_evm.go @@ -22,7 +22,7 @@ func (r *E2ERunner) SetupEVMV2() { r.requireTxSuccessful(receipt, failMessage) } - r.Logger.Print("⚙️ setting up EVM v2 network") + r.Logger.Info("⚙️ setting up EVM v2 network") startTime := time.Now() defer func() { r.Logger.Info("EVM v2 setup took %s\n", time.Since(startTime)) @@ -104,5 +104,10 @@ func (r *E2ERunner) SetupEVMV2() { txWhitelist, err := r.ERC20CustodyV2.Whitelist(r.EVMAuth, r.ERC20Addr) require.NoError(r, err) + // set legacy supported (calling deposit directly in ERC20Custody) + txSetLegacySupported, err := r.ERC20CustodyV2.SetSupportsLegacy(r.EVMAuth, true) + require.NoError(r, err) + ensureTxReceipt(txWhitelist, "ERC20 whitelist failed") + ensureTxReceipt(txSetLegacySupported, "Set legacy support failed") } diff --git a/e2e/runner/v2_setup_zeta.go b/e2e/runner/v2_setup_zeta.go index c82e8fa741..bac11aaae2 100644 --- a/e2e/runner/v2_setup_zeta.go +++ b/e2e/runner/v2_setup_zeta.go @@ -75,9 +75,9 @@ func (r *E2ERunner) SetZEVMContractsV2() { ensureTxReceipt(txTestDAppV2, "TestDAppV2 deployment failed") } -// UpdateChainParamsERC20CustodyContract update the erc20 custody contract in the chain params +// UpdateChainParamsV2Contracts update the erc20 custody contract and gateway address in the chain params // this operation is used when transitioning to new smart contract architecture where a new ERC20 custody contract is deployed -func (r *E2ERunner) UpdateChainParamsERC20CustodyContract() { +func (r *E2ERunner) UpdateChainParamsV2Contracts() { res, err := r.ObserverClient.GetChainParams(r.Ctx, &observertypes.QueryGetChainParamsRequest{}) require.NoError(r, err) @@ -101,6 +101,9 @@ func (r *E2ERunner) UpdateChainParamsERC20CustodyContract() { // update with the new ERC20 custody contract address chainParams.Erc20CustodyContractAddress = r.ERC20CustodyV2Addr.Hex() + // update with the new gateway address + chainParams.GatewayAddress = r.GatewayEVMAddr.Hex() + // update the chain params _, err = r.ZetaTxServer.BroadcastTx(utils.OperationalPolicyName, observertypes.NewMsgUpdateChainParams( r.ZetaTxServer.MustGetAccountAddressFromName(utils.OperationalPolicyName), diff --git a/e2e/txserver/authority.go b/e2e/txserver/authority.go new file mode 100644 index 0000000000..a6c4dfb9dd --- /dev/null +++ b/e2e/txserver/authority.go @@ -0,0 +1,33 @@ +package txserver + +import ( + "fmt" + + e2eutils "github.com/zeta-chain/zetacore/e2e/utils" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" +) + +// AddAuthorization adds a new authorization in the authority module for admin message +func (zts ZetaTxServer) AddAuthorization(msgURL string) error { + // retrieve account + accAdmin, err := zts.clientCtx.Keyring.Key(e2eutils.AdminPolicyName) + if err != nil { + return err + } + addrAdmin, err := accAdmin.GetAddress() + if err != nil { + return err + } + + // add new authorization + _, err = zts.BroadcastTx(e2eutils.AdminPolicyName, authoritytypes.NewMsgAddAuthorization( + addrAdmin.String(), + msgURL, + authoritytypes.PolicyType_groupAdmin, + )) + if err != nil { + return fmt.Errorf("failed to add authorization: %w", err) + } + + return nil +} diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 39a6d8325b..a6aea77fed 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -314,6 +314,7 @@ func (zts ZetaTxServer) UpdateGatewayAddress(account, gatewayAddr string) error addr.String(), gatewayAddr, )) + return err } diff --git a/go.mod b/go.mod index c151309321..7297dc703b 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/zeta-chain/ethermint v0.0.0-20240729121328-43bf9ddbf82f github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2 - github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240816144801-7eb673cf8890 + github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240819143729-b8229cd7b410 gitlab.com/thorchain/tss/go-tss v1.6.5 gitlab.com/thorchain/tss/tss-lib v0.2.0 go.nhat.io/grpcmock v0.25.0 @@ -73,7 +73,7 @@ require ( google.golang.org/protobuf v1.32.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 + gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/sqlite v1.4.4 gorm.io/gorm v1.24.6 ) diff --git a/go.sum b/go.sum index 6eb0a71e76..fa99e25703 100644 --- a/go.sum +++ b/go.sum @@ -1631,8 +1631,8 @@ github.com/zeta-chain/go-tss v0.0.0-20240729195411-9f5ae8189449 h1:4U+4g2QQjbrme github.com/zeta-chain/go-tss v0.0.0-20240729195411-9f5ae8189449/go.mod h1:LN1IBRN8xQkKgdgLhl5BDGZyPm70QOTbVLejdS2FVpo= github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2 h1:gd2uE0X+ZbdFJ8DubxNqLbOVlCB12EgWdzSNRAR82tM= github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2/go.mod h1:x7Bkwbzt2W2lQfjOirnff0Dj+tykdbTG1FMJPVPZsvE= -github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240816144801-7eb673cf8890 h1:y2TNtm9ZF/GjJIg40wiZ/IqoeDouFfqi27Uu3xSQaVE= -github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240816144801-7eb673cf8890/go.mod h1:SjT7QirtJE8stnAe1SlNOanxtfSfijJm3MGJ+Ax7w7w= +github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240819143729-b8229cd7b410 h1:sBeVX63s/qmfT1KnIKj1Y2SK3PsFpAM/P49ODcD1CN8= +github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240819143729-b8229cd7b410/go.mod h1:SjT7QirtJE8stnAe1SlNOanxtfSfijJm3MGJ+Ax7w7w= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= diff --git a/x/fungible/keeper/msg_server_update_gateway_contract.go b/x/fungible/keeper/msg_server_update_gateway_contract.go index 1fd3b95021..05ad086d75 100644 --- a/x/fungible/keeper/msg_server_update_gateway_contract.go +++ b/x/fungible/keeper/msg_server_update_gateway_contract.go @@ -5,6 +5,8 @@ import ( cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + ethcommon "github.com/ethereum/go-ethereum/common" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" "github.com/zeta-chain/zetacore/x/fungible/types" @@ -21,6 +23,16 @@ func (k msgServer) UpdateGatewayContract( return nil, cosmoserrors.Wrap(authoritytypes.ErrUnauthorized, err.Error()) } + // parse the new gateway address + gatewayAddr := ethcommon.HexToAddress(msg.NewGatewayContractAddress) + if gatewayAddr == (ethcommon.Address{}) { + return nil, cosmoserrors.Wrapf( + sdkerrors.ErrInvalidAddress, + "invalid gateway contract address (%s)", + msg.NewGatewayContractAddress, + ) + } + // The SystemContract state variable tracks the contract addresses used by the protocol // This variable is planned to be renamed ProtocolContracts in the future: // https://github.com/zeta-chain/node/issues/2576 @@ -32,10 +44,30 @@ func (k msgServer) UpdateGatewayContract( } oldGateway := protocolContracts.Gateway - // update address and save + // update all ZRC20 contracts with the new gateway address + foreignCoins := k.GetAllForeignCoins(ctx) + for _, fcoin := range foreignCoins { + zrc20Addr := ethcommon.HexToAddress(fcoin.Zrc20ContractAddress) + if zrc20Addr == (ethcommon.Address{}) { + k.Logger(ctx).Error("invalid zrc20 contract address", "address", fcoin.Zrc20ContractAddress) + continue + } + + _, err := k.CallUpdateGatewayAddress(ctx, zrc20Addr, gatewayAddr) + if err != nil { + return nil, cosmoserrors.Wrapf( + err, + "failed to call updateSystemContractAddress for ZRC20 (%s)", + fcoin.Zrc20ContractAddress, + ) + } + } + + // update in the store address and save protocolContracts.Gateway = msg.NewGatewayContractAddress k.SetSystemContract(ctx, protocolContracts) + // emit event err = ctx.EventManager().EmitTypedEvent( &types.EventGatewayContractUpdated{ MsgTypeUrl: sdk.MsgTypeURL(&types.MsgUpdateGatewayContract{}), diff --git a/x/fungible/keeper/msg_server_update_gateway_contract_test.go b/x/fungible/keeper/msg_server_update_gateway_contract_test.go index 70b0a1144c..f28b1921f3 100644 --- a/x/fungible/keeper/msg_server_update_gateway_contract_test.go +++ b/x/fungible/keeper/msg_server_update_gateway_contract_test.go @@ -1,6 +1,9 @@ package keeper_test import ( + "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" + "github.com/zeta-chain/zetacore/pkg/chains" "testing" "github.com/stretchr/testify/require" @@ -12,46 +15,81 @@ import ( ) func TestKeeper_UpdateGatewayContract(t *testing.T) { - t.Run("can update the gateway contract address stored in the module", func(t *testing.T) { - // ARRANGE - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseAuthorityMock: true, - }) - - msgServer := keeper.NewMsgServerImpl(*k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - admin := sample.AccAddress() + t.Run( + "can update the gateway contract address stored in the module and update address in ZRC20s", + func(t *testing.T) { + // ARRANGE + k, ctx, sdkk, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseAuthorityMock: true, + }) - authorityMock := keepertest.GetFungibleAuthorityMock(t, k) + msgServer := keeper.NewMsgServerImpl(*k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + admin := sample.AccAddress() - systemContractAddr := sample.EthAddress() - connectorAddr := sample.EthAddress() - k.SetSystemContract(ctx, types.SystemContract{ - SystemContract: systemContractAddr.Hex(), - ConnectorZevm: connectorAddr.Hex(), - Gateway: sample.EthAddress().Hex(), - }) + authorityMock := keepertest.GetFungibleAuthorityMock(t, k) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + // setup gas coins for two chains + defaultChains := chains.DefaultChainsList() + require.True(t, len(defaultChains) > 1) + require.NotNil(t, defaultChains[0]) + require.NotNil(t, defaultChains[1]) + chainID1 := defaultChains[0].ChainId + chainID2 := defaultChains[1].ChainId + _, _, _, connectorAddr, systemContractAddr := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + gas1 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID1, "foo", "foo") + gas2 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID2, "bar", "bar") + queryZRC20Gateway := func(contract common.Address) string { + abi, err := zrc20.ZRC20MetaData.GetAbi() + require.NoError(t, err) + res, err := k.CallEVM( + ctx, + *abi, + types.ModuleAddressEVM, + contract, + keeper.BigIntZero, + nil, + false, + false, + "gatewayAddress", + ) + require.NoError(t, err) + unpacked, err := abi.Unpack("gatewayAddress", res.Ret) + require.NoError(t, err) + address, ok := unpacked[0].(common.Address) + require.True(t, ok) + return address.Hex() + } + + // new gateway address + newGatewayAddr := sample.EthAddress() + require.NotEqual(t, newGatewayAddr.Hex(), queryZRC20Gateway(gas1)) + require.NotEqual(t, newGatewayAddr.Hex(), queryZRC20Gateway(gas2)) - newGatewayAddr := sample.EthAddress() + msg := types.NewMsgUpdateGatewayContract(admin, newGatewayAddr.Hex()) + keepertest.MockCheckAuthorization(&authorityMock.Mock, msg, nil) - msg := types.NewMsgUpdateGatewayContract(admin, newGatewayAddr.Hex()) - keepertest.MockCheckAuthorization(&authorityMock.Mock, msg, nil) + // ACT + _, err := msgServer.UpdateGatewayContract(ctx, msg) - // ACT - _, err := msgServer.UpdateGatewayContract(ctx, msg) + // ASSERT + require.NoError(t, err) + sc, found := k.GetSystemContract(ctx) + require.True(t, found) - // ASSERT - require.NoError(t, err) - sc, found := k.GetSystemContract(ctx) - require.True(t, found) + // gateway is updated + require.EqualValues(t, newGatewayAddr.Hex(), sc.Gateway) - // gateway is updated - require.EqualValues(t, newGatewayAddr.Hex(), sc.Gateway) + // system contract and connector remain the same + require.EqualValues(t, systemContractAddr.Hex(), sc.SystemContract) + require.EqualValues(t, connectorAddr.Hex(), sc.ConnectorZevm) - // system contract and connector remain the same - require.EqualValues(t, systemContractAddr.Hex(), sc.SystemContract) - require.EqualValues(t, connectorAddr.Hex(), sc.ConnectorZevm) - }) + // gateway address in ZRC20s is updated + require.EqualValues(t, newGatewayAddr.Hex(), queryZRC20Gateway(gas1)) + require.EqualValues(t, newGatewayAddr.Hex(), queryZRC20Gateway(gas2)) + }, + ) t.Run( "can update and overwrite the gateway contract if system contract state variable not found", @@ -104,15 +142,37 @@ func TestKeeper_UpdateGatewayContract(t *testing.T) { admin := sample.AccAddress() authorityMock := keepertest.GetFungibleAuthorityMock(t, k) - msg := types.NewMsgUpdateSystemContract(admin, sample.EthAddress().Hex()) + msg := types.NewMsgUpdateGatewayContract(admin, sample.EthAddress().Hex()) keepertest.MockCheckAuthorization(&authorityMock.Mock, msg, authoritytypes.ErrUnauthorized) // ACT - _, err := msgServer.UpdateSystemContract(ctx, msg) + _, err := msgServer.UpdateGatewayContract(ctx, msg) // ASSERT require.Error(t, err) require.ErrorIs(t, err, authoritytypes.ErrUnauthorized) }) + t.Run("should prevent update the gateway contract if invalid gateway address", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseAuthorityMock: true, + }) + + msgServer := keeper.NewMsgServerImpl(*k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + admin := sample.AccAddress() + + authorityMock := keepertest.GetFungibleAuthorityMock(t, k) + + msg := types.NewMsgUpdateGatewayContract(admin, "invalid") + keepertest.MockCheckAuthorization(&authorityMock.Mock, msg, nil) + + // ACT + _, err := msgServer.UpdateGatewayContract(ctx, msg) + + // ASSERT + require.Error(t, err) + require.Contains(t, err.Error(), "invalid gateway contract address") + }) } diff --git a/x/fungible/keeper/v2_evm.go b/x/fungible/keeper/v2_evm.go index 3015471f1c..b5eab2ac8d 100644 --- a/x/fungible/keeper/v2_evm.go +++ b/x/fungible/keeper/v2_evm.go @@ -9,11 +9,38 @@ import ( "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" "github.com/zeta-chain/protocol-contracts/v2/pkg/revert.sol" "github.com/zeta-chain/protocol-contracts/v2/pkg/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/pkg/crypto" "github.com/zeta-chain/zetacore/x/fungible/types" ) +// CallUpdateGatewayAddress calls the updateGatewayAddress function on the ZRC20 contract +// function updateGatewayAddress(address addr) +func (k Keeper) CallUpdateGatewayAddress( + ctx sdk.Context, + zrc20Address common.Address, + newGatewayAddress common.Address, +) (*evmtypes.MsgEthereumTxResponse, error) { + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + return nil, err + } + + return k.CallEVM( + ctx, + *zrc20ABI, + types.ModuleAddressEVM, + zrc20Address, + BigIntZero, + nil, + true, + false, + "updateGatewayAddress", + newGatewayAddress, + ) +} + // CallDepositAndCallZRC20 calls the depositAndCall (ZRC20 version) function on the gateway contract // Callable only by the fungible module account // returns directly CallEVM() @@ -166,13 +193,14 @@ func (k Keeper) CallExecuteRevert( // CallDepositAndRevert calls the depositAndRevert function on the gateway contract // -//function depositAndRevert( +// function depositAndRevert( +// // address zrc20, // uint256 amount, // address target, // RevertContext revertContext -//) - +// +// ) func (k Keeper) CallDepositAndRevert( ctx sdk.Context, zrc20 common.Address, diff --git a/zetaclient/chains/bitcoin/errors.go b/zetaclient/chains/bitcoin/errors.go new file mode 100644 index 0000000000..d04d67687d --- /dev/null +++ b/zetaclient/chains/bitcoin/errors.go @@ -0,0 +1,6 @@ +package bitcoin + +import "errors" + +// ErrBitcoinNotEnabled is the error returned when bitcoin is not enabled +var ErrBitcoinNotEnabled = errors.New("bitcoin is not enabled") diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 2035340e73..99a11e373f 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -56,7 +56,14 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { } err := ob.ObserveInbound(ctx) if err != nil { - ob.logger.Inbound.Error().Err(err).Msg("WatchInbound error observing in tx") + // skip showing log for block number 0 as it means Bitcoin node is not enabled + // TODO: prevent this routine from running if Bitcoin node is not enabled + // https://github.com/zeta-chain/node/issues/2790 + if !errors.Is(err, bitcoin.ErrBitcoinNotEnabled) { + ob.logger.Inbound.Error().Err(err).Msg("WatchInbound error observing in tx") + } else { + ob.logger.Inbound.Debug().Err(err).Msg("WatchInbound: Bitcoin node is not enabled") + } } ticker.UpdateInterval(ob.GetChainParams().InboundTicker, ob.logger.Inbound) case <-ob.StopChannel(): @@ -72,20 +79,26 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { zetaCoreClient := ob.ZetacoreClient() // get and update latest block height - cnt, err := ob.btcClient.GetBlockCount() + currentBlock, err := ob.btcClient.GetBlockCount() if err != nil { return fmt.Errorf("observeInboundBTC: error getting block number: %s", err) } - if cnt < 0 { - return fmt.Errorf("observeInboundBTC: block number is negative: %d", cnt) + if currentBlock < 0 { + return fmt.Errorf("observeInboundBTC: block number is negative: %d", currentBlock) + } + + // 0 will be returned if the node is not synced + if currentBlock == 0 { + return errors.Wrap(bitcoin.ErrBitcoinNotEnabled, "observeInboundBTC: current block number 0 is too low") } + // #nosec G115 checked positive - lastBlock := uint64(cnt) + lastBlock := uint64(currentBlock) if lastBlock < ob.LastBlock() { return fmt.Errorf( "observeInboundBTC: block number should not decrease: current %d last %d", - cnt, + currentBlock, ob.LastBlock(), ) } @@ -93,7 +106,7 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { // skip if current height is too low if lastBlock < ob.GetChainParams().ConfirmationCount { - return fmt.Errorf("observeInboundBTC: skipping observer, current block number %d is too low", cnt) + return fmt.Errorf("observeInboundBTC: skipping observer, current block number %d is too low", currentBlock) } // skip if no new block is confirmed @@ -111,7 +124,7 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { return err } ob.logger.Inbound.Info().Msgf("observeInboundBTC: block %d has %d txs, current block %d, last block %d", - blockNumber, len(res.Block.Tx), cnt, lastScanned) + blockNumber, len(res.Block.Tx), currentBlock, lastScanned) // add block header to zetacore if len(res.Block.Tx) > 1 { diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go index d8b7378b58..dea0ff7216 100644 --- a/zetaclient/chains/bitcoin/observer/observer.go +++ b/zetaclient/chains/bitcoin/observer/observer.go @@ -8,6 +8,7 @@ import ( "math" "math/big" "sort" + "strings" "time" "github.com/btcsuite/btcd/btcjson" @@ -455,7 +456,15 @@ func (ob *Observer) WatchUTXOs(ctx context.Context) error { } err := ob.FetchUTXOs(ctx) if err != nil { - ob.logger.UTXOs.Error().Err(err).Msg("error fetching btc utxos") + // log debug log if the error if no wallet is loaded + // this is to prevent extensive logging in localnet when the wallet is not loaded for non-Bitcoin test + // TODO: prevent this routine from running if Bitcoin node is not enabled + // https://github.com/zeta-chain/node/issues/2790 + if !strings.Contains(err.Error(), "No wallet is loaded") { + ob.logger.UTXOs.Error().Err(err).Msg("error fetching btc utxos") + } else { + ob.logger.UTXOs.Debug().Err(err).Msg("No wallet is loaded") + } } ticker.UpdateInterval(ob.GetChainParams().WatchUtxoTicker, ob.logger.UTXOs) case <-ob.StopChannel(): diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index c77d4e36e6..a25c3962f5 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -115,9 +115,10 @@ func (signer *Signer) SetERC20CustodyAddress(addr ethcommon.Address) { } // SetGatewayAddress sets the gateway address -func (signer *Signer) SetGatewayAddress(_ string) { - // Note: do nothing for now - // gateway address will be needed in the future contract architecture +func (signer *Signer) SetGatewayAddress(addr string) { + signer.Lock() + defer signer.Unlock() + signer.gatewayAddress = ethcommon.HexToAddress(addr) } // GetZetaConnectorAddress returns the zeta connector address diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index d8a8aeb6a0..7c7df03ca8 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -161,7 +161,7 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte case chain.IsEVM(): params := chain.Params() - // update zeta connector and ERC20 custody addresses + // update zeta connector, ERC20 custody, and gateway addresses zetaConnectorAddress := ethcommon.HexToAddress(params.GetConnectorContractAddress()) if zetaConnectorAddress != signer.GetZetaConnectorAddress() { signer.SetZetaConnectorAddress(zetaConnectorAddress) @@ -169,14 +169,20 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte Str("signer.connector_address", zetaConnectorAddress.String()). Msgf("updated zeta connector address for chain %d", chainID) } - erc20CustodyAddress := ethcommon.HexToAddress(params.GetErc20CustodyContractAddress()) if erc20CustodyAddress != signer.GetERC20CustodyAddress() { signer.SetERC20CustodyAddress(erc20CustodyAddress) oc.logger.Info(). Str("signer.erc20_custody", erc20CustodyAddress.String()). - Msgf("updated zeta connector address for chain %d", chainID) + Msgf("updated erc20 custody address for chain %d", chainID) + } + if params.GatewayAddress != signer.GetGatewayAddress() { + signer.SetGatewayAddress(params.GatewayAddress) + oc.logger.Info(). + Str("signer.gateway_address", params.GatewayAddress). + Msgf("updated gateway address for chain %d", chainID) } + case chain.IsSolana(): params := chain.Params() @@ -368,17 +374,6 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { chainID := chain.ID() - // get cctxs from map and set pending transactions prometheus gauge - cctxList := cctxMap[chainID] - - metrics.PendingTxsPerChain. - WithLabelValues(chain.Name()). - Set(float64(len(cctxList))) - - if len(cctxList) == 0 { - continue - } - // update chain parameters for signer and chain observer signer, err := oc.resolveSigner(app, chainID) if err != nil { @@ -394,6 +389,17 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { continue } + // get cctxs from map and set pending transactions prometheus gauge + cctxList := cctxMap[chainID] + + metrics.PendingTxsPerChain. + WithLabelValues(chain.Name()). + Set(float64(len(cctxList))) + + if len(cctxList) == 0 { + continue + } + if !app.IsOutboundObservationEnabled() { continue }