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 }