From 48fc41640df8dca919f71fcf8fba525313435b59 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 18 Jun 2024 18:29:18 +0200 Subject: [PATCH 01/18] Add debugging capabilities to e2e tests (Delve) --- docs/development/LOCAL_TESTING.md | 2 +- e2e/README.md | 36 +++++++++++++++++++++++++------ e2e/scripts/debug.sh | 28 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100755 e2e/scripts/debug.sh diff --git a/docs/development/LOCAL_TESTING.md b/docs/development/LOCAL_TESTING.md index 8ba3893a04..d28c44638e 100644 --- a/docs/development/LOCAL_TESTING.md +++ b/docs/development/LOCAL_TESTING.md @@ -88,7 +88,7 @@ $ docker logs -f orchestrator To stop the tests, ```bash -make stop-test +make stop-localnet ``` ### Run monitoring setup diff --git a/e2e/README.md b/e2e/README.md index 9c53af86b3..4c44a0056e 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,24 +1,33 @@ # `e2e` -`e2e` is a comprehensive suite of E2E tests designed to validate the integration and functionality of the ZetaChain network, particularly its interactions with Bitcoin and EVM (Ethereum Virtual Machine) networks. This tool is essential for ensuring the robustness and reliability of ZetaChain's cross-chain functionalities. +`e2e` is a comprehensive suite of E2E tests designed to validate the integration and functionality of the ZetaChain +network, particularly its interactions with Bitcoin and EVM (Ethereum Virtual Machine) networks. This tool is essential +for ensuring the robustness and reliability of ZetaChain's cross-chain functionalities. ## Packages + The E2E testing project is organized into several packages, each with a specific role: -- `config`: Provides general configuration for E2E tests, including RPC addresses for connected networks, addresses of deployed smart contracts, and account details for test transactions. +- `config`: Provides general configuration for E2E tests, including RPC addresses for connected networks, addresses of + deployed smart contracts, and account details for test transactions. - `contracts`: Includes sample Solidity smart contracts used in testing scenarios. - `runner`: Responsible for executing E2E tests, handling interactions with various network clients. -- `e2etests`: Houses a collection of E2E tests that can be run against the ZetaChain network. Each test is implemented as a separate Go file prefixed with `test_`. +- `e2etests`: Houses a collection of E2E tests that can be run against the ZetaChain network. Each test is implemented + as a separate Go file prefixed with `test_`. - `txserver`: A minimalistic client for interacting with the ZetaChain RPC interface. -- `utils`: Offers utility functions to facilitate interactions with the different blockchain networks involved in testing. +- `utils`: Offers utility functions to facilitate interactions with the different blockchain networks involved in + testing. ## Config -The E2E testing suite utilizes a flexible and comprehensive configuration system defined in the config package, which is central to setting up and customizing your test environments. The configuration is structured as follows: +The E2E testing suite utilizes a flexible and comprehensive configuration system defined in the config package, which is +central to setting up and customizing your test environments. The configuration is structured as follows: -A config YAML file can be provided to the E2E test tool via the `--config` flag. If no config file is provided, the tool will use default values for all configuration parameters. +A config YAML file can be provided to the E2E test tool via the `--config` flag. If no config file is provided, the tool +will use default values for all configuration parameters. ### Config Structure + - `RPCs`: Defines the RPC endpoints for various networks involved in the testing. - `Contracts`: Specifies the addresses of pre-deployed smart contracts relevant to the tests. - `ZetaChainID`: The specific chain ID of the ZetaChain network being tested. @@ -34,6 +43,7 @@ A config YAML file can be provided to the E2E test tool via the `--config` flag. ### Contracts Configuration: **EVM Contracts** + - `ZetaEthAddress`: Address of Zeta token contract on EVM chain. - `ConnectorEthAddr`: Address of a connector contract on EVM chain. - `ERC20`: Address of the ERC20 token contract on EVM chain. @@ -56,3 +66,17 @@ zeta_chain_id: "zetachain-1" ``` NOTE: config is in progress, contracts on the zEVM must be added + +## Debugging + +It's possible to debug a single test using Delve debugger. + +1. Make sure delve is installed. `go install github.com/go-delve/delve/cmd/dlv@latest` +2. Configure your IDE to use Delve as the debugger. For Goland, you can do the following: + - Go to "Run" > "Edit Run Configurations" + - Hit "+" > "Go Remote". Keep port as default (`2345`). Toggle "On Disconnect" > "Stop Delve process" +3. Make sure that localnet is running. For a quick start, you can use `make start-localnet-skip-build`. + Networks need some time to generate blocks. +4. Run test as following: `./e2e/scripts/debug.sh my_test_name arg1 arg2 arg_n` +5. Place a breakpoint in the code. +6. Go to the editor's debug panel and hit "Debug" button. diff --git a/e2e/scripts/debug.sh b/e2e/scripts/debug.sh new file mode 100755 index 0000000000..2020bdab01 --- /dev/null +++ b/e2e/scripts/debug.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Exit on any error +set -e + +# Make sure that dlv is installed! +# go install github.com/go-delve/delve/cmd/dlv@latest + +# Check if at least one argument is provided +if [ "$#" -lt 1 ]; then + echo "Usage: $0 [args...]" + exit 1 +fi + +# Extract the test argument +test=$1 +shift + +# Collect additional arguments +e2e_test_args=$(echo "$@" | tr ' ' ',') + +dlv_opts="--headless --listen=:2345 --api-version=2 --accept-multiclient" +e2e_config="cmd/zetae2e/config/local.yml" + +# Echo commands +# shellcheck disable=SC2086 +set -x +dlv debug ./cmd/zetae2e/ $dlv_opts -- run $test:$e2e_test_args --config $e2e_config \ No newline at end of file From 9d0372594420d06e6768a4fcbf69ab6f0e514e31 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 18 Jun 2024 18:29:41 +0200 Subject: [PATCH 02/18] Add `make start-localnet-skip-build` --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 480c956c9a..a97bde5c3a 100644 --- a/Makefile +++ b/Makefile @@ -248,11 +248,13 @@ start-upgrade-test-light: zetanode-upgrade @echo "--> Starting light upgrade test (no ZetaChain state populating before upgrade)" cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-upgrade.yml -f docker-compose-upgrade-light.yml up -d -start-localnet: zetanode +start-localnet: zetanode start-localnet-skip-build + +start-localnet-skip-build: @echo "--> Starting localnet" cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-setup-only.yml up -d -stop-test: +stop-localnet: cd contrib/localnet/ && $(DOCKER) compose down --remove-orphans ############################################################################### From 55405551de0dcc8c8ebae81be7f83ddcd3840b75 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 18 Jun 2024 18:33:36 +0200 Subject: [PATCH 03/18] Bump github.com/rjeczalik/notify to fix Apple ARM issue (https://github.com/rjeczalik/notify/issues/212) --- go.mod | 2 ++ go.sum | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 709983e8cc..60a7598f16 100644 --- a/go.mod +++ b/go.mod @@ -347,6 +347,8 @@ replace ( github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + + github.com/rjeczalik/notify => github.com/rjeczalik/notify v0.9.3 // replace broken goleveldb github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 ) diff --git a/go.sum b/go.sum index ac3eb6cc0b..a5bbf02ee3 100644 --- a/go.sum +++ b/go.sum @@ -1478,8 +1478,8 @@ github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNw github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= +github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -2023,6 +2023,7 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From c13d7f02945da41c4388459cc4cccbc56f2d446a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 18 Jun 2024 20:26:51 +0200 Subject: [PATCH 04/18] Add run.sh for a single e2e test run --- e2e/README.md | 3 ++- e2e/scripts/debug.sh | 8 ++++---- e2e/scripts/run.sh | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100755 e2e/scripts/run.sh diff --git a/e2e/README.md b/e2e/README.md index 4c44a0056e..dc2ad442fa 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -77,6 +77,7 @@ It's possible to debug a single test using Delve debugger. - Hit "+" > "Go Remote". Keep port as default (`2345`). Toggle "On Disconnect" > "Stop Delve process" 3. Make sure that localnet is running. For a quick start, you can use `make start-localnet-skip-build`. Networks need some time to generate blocks. -4. Run test as following: `./e2e/scripts/debug.sh my_test_name arg1 arg2 arg_n` +4. Run test as following: `./e2e/scripts/debug.sh my_test_name arg1 arg2 arg_n`. + Example: `./e2e/scripts/debug.sh bitcoin_withdraw_restricted 0.001` 5. Place a breakpoint in the code. 6. Go to the editor's debug panel and hit "Debug" button. diff --git a/e2e/scripts/debug.sh b/e2e/scripts/debug.sh index 2020bdab01..87787b505f 100755 --- a/e2e/scripts/debug.sh +++ b/e2e/scripts/debug.sh @@ -16,13 +16,13 @@ fi test=$1 shift +dlv_opts="--headless --listen=:2345 --api-version=2 --accept-multiclient" + # Collect additional arguments e2e_test_args=$(echo "$@" | tr ' ' ',') - -dlv_opts="--headless --listen=:2345 --api-version=2 --accept-multiclient" -e2e_config="cmd/zetae2e/config/local.yml" +e2e_opts="--config cmd/zetae2e/config/local.yml --skip-header-proof --skip-setup" # Echo commands # shellcheck disable=SC2086 set -x -dlv debug ./cmd/zetae2e/ $dlv_opts -- run $test:$e2e_test_args --config $e2e_config \ No newline at end of file +dlv debug ./cmd/zetae2e/ $dlv_opts -- run $test:$e2e_test_args $e2e_opts \ No newline at end of file diff --git a/e2e/scripts/run.sh b/e2e/scripts/run.sh new file mode 100755 index 0000000000..0243343dff --- /dev/null +++ b/e2e/scripts/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Exit on any error +set -e + +# Make sure that dlv is installed! +# go install github.com/go-delve/delve/cmd/dlv@latest + +# Check if at least one argument is provided +if [ "$#" -lt 1 ]; then + echo "Usage: $0 [args...]" + exit 1 +fi + +# Extract the test argument +test=$1 +shift + +# Collect additional arguments +e2e_test_args=$(echo "$@" | tr ' ' ',') +e2e_opts="--config cmd/zetae2e/config/local.yml --skip-header-proof --skip-setup" + +# Echo commands +# shellcheck disable=SC2086 +set -x +go run ./cmd/zetae2e/ run $test:$e2e_test_args --config $e2e_opts \ No newline at end of file From acc367416c4d24bf4e533cfd43868dee6f528bcf Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 18 Jun 2024 20:27:54 +0200 Subject: [PATCH 05/18] Add testify support for e2e runner --- e2e/runner/runner.go | 14 +++++++++++--- e2e/runner/setup_bitcoin.go | 7 +++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index d8df023057..368f517fab 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -108,7 +108,6 @@ type E2ERunner struct { Ctx context.Context CtxCancel context.CancelFunc Logger *Logger - WG sync.WaitGroup BitcoinParams *chaincfg.Params mutex sync.Mutex } @@ -158,8 +157,6 @@ func NewE2ERunner( BtcRPCClient: btcRPCClient, Logger: logger, - - WG: sync.WaitGroup{}, } } @@ -285,3 +282,14 @@ func (runner *E2ERunner) PrintContractAddresses() { runner.Logger.Print("ERC20: %s", runner.ERC20Addr.Hex()) runner.Logger.Print("TestDappEVM: %s", runner.EvmTestDAppAddr.Hex()) } + +// Errorf logs an error message. Mimics the behavior of testing.T.Errorf +func (runner *E2ERunner) Errorf(format string, args ...any) { + runner.Logger.Error(format, args...) +} + +// FailNow implemented to mimic the behavior of testing.T.FailNow +func (runner *E2ERunner) FailNow() { + runner.Logger.Error("Test failed") + os.Exit(1) +} diff --git a/e2e/runner/setup_bitcoin.go b/e2e/runner/setup_bitcoin.go index 15af182915..8e39d0143e 100644 --- a/e2e/runner/setup_bitcoin.go +++ b/e2e/runner/setup_bitcoin.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcutil" + "github.com/stretchr/testify/require" ) func (runner *E2ERunner) SetupBitcoinAccount(initNetwork bool) { @@ -85,10 +86,8 @@ func (runner *E2ERunner) SetBtcAddress(name string, rescan bool) { } if rescan { - err = runner.BtcRPCClient.ImportPrivKeyRescan(privkeyWIF, name, true) - if err != nil { - panic(err) - } + err := runner.BtcRPCClient.ImportPrivKeyRescan(privkeyWIF, name, true) + require.NoError(runner, err, "failed to execute ImportPrivKeyRescan") } runner.BTCDeployerAddress, err = btcutil.NewAddressWitnessPubKeyHash( From 1ddaa2d205d523d5bf3a0f25267535e3f7af069b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 18 Jun 2024 20:28:36 +0200 Subject: [PATCH 06/18] Minor refactoring --- cmd/zetae2e/stress.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/cmd/zetae2e/stress.go b/cmd/zetae2e/stress.go index 20e7ff494f..e2258d0821 100644 --- a/cmd/zetae2e/stress.go +++ b/cmd/zetae2e/stress.go @@ -7,6 +7,7 @@ import ( "math/big" "os" "sort" + "sync" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -225,11 +226,25 @@ func StressTest(cmd *cobra.Command, _ []string) { fmt.Println(" 1. Periodically Withdraw ETH from ZEVM to EVM") fmt.Println(" 2. Display Network metrics to monitor performance [Num Pending outbound tx], [Num Trackers]") - e2eTest.WG.Add(2) - go WithdrawCCtx(e2eTest) // Withdraw from ZEVM to EVM - go EchoNetworkMetrics(e2eTest) // Display Network metrics periodically to monitor performance + var wg sync.WaitGroup - e2eTest.WG.Wait() + wg.Add(2) + + go func() { + defer wg.Done() + + // Withdraw from ZEVM to EVM + WithdrawCCtx(e2eTest) + }() + + go func() { + defer wg.Done() + + // Display Network metrics periodically to monitor performance + EchoNetworkMetrics(e2eTest) + }() + + wg.Wait() } // WithdrawCCtx withdraw ETHZRC20 from ZEVM to EVM From ecb37c71da6b70c38f9c6198d1431c77566ed094 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 18 Jun 2024 20:30:13 +0200 Subject: [PATCH 07/18] WIP TestDepositBTCRefund --- e2e/e2etests/e2etests.go | 8 ++ e2e/e2etests/helper_bitcoin.go | 38 +++------ e2e/e2etests/test_bitcoin_deposit_refund.go | 93 +++++++++------------ 3 files changed, 59 insertions(+), 80 deletions(-) diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 348a2a7ef1..345a835ccb 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -56,6 +56,7 @@ const ( Test transfer of Bitcoin asset across chains */ TestBitcoinDepositName = "bitcoin_deposit" + TestBitcoinDepositRefund = "bitcoin_deposit_refund" TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit" TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot" TestBitcoinWithdrawLegacyName = "bitcoin_withdraw_legacy" @@ -332,6 +333,13 @@ var AllE2ETests = []runner.E2ETest{ }, TestBitcoinDeposit, ), + runner.NewE2ETest( + TestBitcoinDepositRefund, + "deposit Bitcoin into ZEVM; expect refund", []runner.ArgDefinition{ + {Description: "amount in btc", DefaultValue: "0.001"}, + }, + TestDepositBTCRefund, + ), runner.NewE2ETest( TestBitcoinWithdrawSegWitName, "withdraw BTC from ZEVM to a SegWit address", diff --git a/e2e/e2etests/helper_bitcoin.go b/e2e/e2etests/helper_bitcoin.go index 09cb4ded96..220b7040bd 100644 --- a/e2e/e2etests/helper_bitcoin.go +++ b/e2e/e2etests/helper_bitcoin.go @@ -1,13 +1,13 @@ package e2etests import ( - "fmt" "math/big" "strconv" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcutil" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" @@ -55,50 +55,36 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) r.BTCZRC20Addr, big.NewInt(amount.Int64()*2), ) // approve more to cover withdraw fee - if err != nil { - panic(err) - } + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - if receipt.Status != 1 { - panic(fmt.Errorf("approve receipt status is not 1")) - } + require.Equal(r, uint64(1), receipt.Status, "approve receipt status is not 1") // mine blocks if testing on regnet stop := r.MineBlocksIfLocalBitcoin() // withdraw 'amount' of BTC from ZRC20 to BTC address tx, err = r.BTCZRC20.Withdraw(r.ZEVMAuth, []byte(to.EncodeAddress()), amount) - if err != nil { - panic(err) - } + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - if receipt.Status != 1 { - panic(fmt.Errorf("withdraw receipt status is not 1")) - } + require.Equal(r, uint64(1), receipt.Status, "withdraw receipt status is not 1") - // mine 10 blocks to confirm the withdraw tx + // mine 10 blocks to confirm the withdrawal tx _, err = r.GenerateToAddressIfLocalBitcoin(10, to) - if err != nil { - panic(err) - } + require.NoError(r, err) // get cctx and check status cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) - if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { - panic(fmt.Errorf("cctx status is not OutboundMined")) - } + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status, "cctx status is not OutboundMined") // get bitcoin tx according to the outTxHash in cctx outTxHash := cctx.GetCurrentOutboundParam().Hash hash, err := chainhash.NewHashFromStr(outTxHash) - if err != nil { - panic(err) - } + require.NoError(r, err) rawTx, err := r.BtcRPCClient.GetRawTransactionVerbose(hash) - if err != nil { - panic(err) - } + require.NoError(r, err) r.Logger.Info("raw tx:") r.Logger.Info(" TxIn: %d", len(rawTx.Vin)) for idx, txIn := range rawTx.Vin { diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go index ad684e7a90..2f976647c9 100644 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ b/e2e/e2etests/test_bitcoin_deposit_refund.go @@ -1,56 +1,41 @@ package e2etests -// DepositBTCRefund ... -// TODO: define e2e test -// https://github.com/zeta-chain/node-private/issues/79 -//func DepositBTCRefund(r *runner.E2ERunner) { -// r.Logger.InfoLoud("Deposit BTC with invalid memo; should be refunded") -// btc := r.BtcRPCClient -// utxos, err := r.BtcRPCClient.ListUnspent() -// if err != nil { -// panic(err) -// } -// spendableAmount := 0.0 -// spendableUTXOs := 0 -// for _, utxo := range utxos { -// if utxo.Spendable { -// spendableAmount += utxo.Amount -// spendableUTXOs++ -// } -// } -// r.Logger.Info("ListUnspent:") -// r.Logger.Info(" spendableAmount: %f", spendableAmount) -// r.Logger.Info(" spendableUTXOs: %d", spendableUTXOs) -// r.Logger.Info("Now sending two txs to TSS address...") -// _, err = r.SendToTSSFromDeployerToDeposit(r.BTCTSSAddress, 1.1, utxos[:2], btc, r.BTCDeployerAddress) -// if err != nil { -// panic(err) -// } -// _, err = r.SendToTSSFromDeployerToDeposit(r.BTCTSSAddress, 0.05, utxos[2:4], btc, r.BTCDeployerAddress) -// if err != nil { -// panic(err) -// } -// -// r.Logger.Info("testing if the deposit into BTC ZRC20 is successful...") -// -// // check if the deposit is successful -// initialBalance, err := r.BTCZRC20.BalanceOf(&bind.CallOpts{}, r.DeployerAddress) -// if err != nil { -// panic(err) -// } -// for { -// time.Sleep(3 * time.Second) -// balance, err := r.BTCZRC20.BalanceOf(&bind.CallOpts{}, r.DeployerAddress) -// if err != nil { -// panic(err) -// } -// diff := big.NewInt(0) -// diff.Sub(balance, initialBalance) -// if diff.Cmp(big.NewInt(1.15*btcutil.SatoshiPerBitcoin)) != 0 { -// r.Logger.Info("waiting for BTC balance to show up in ZRC contract... current bal %d", balance) -// } else { -// r.Logger.Info("BTC balance is in ZRC contract! Success") -// break -// } -// } -//} +import ( + "strconv" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" +) + +func TestDepositBTCRefund(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given amount to send + require.Len(r, args, 1) + amount := parseFloat(r, args[0]) + + // Given BTC address + r.SetBtcAddress(r.Name, false) + + // Given a list of UTXOs + utxos, err := r.BtcRPCClient.ListUnspent() + require.NoError(r, err) + require.NotEmpty(r, utxos) + + // ACT + // Send a single UTXO to TSS address + txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, []byte("gibberish-memo")) + require.NotEmpty(r, err) + + // Wait for processing in zetaclient + utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) + + // ASSERT + // todo... +} + +func parseFloat(t require.TestingT, s string) float64 { + f, err := strconv.ParseFloat(s, 64) + require.NoError(t, err, "unable to parse float %q", s) + return f +} From fc21fa761359262ca64b4f4b68fee5c502fab7e8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 19 Jun 2024 12:28:21 +0200 Subject: [PATCH 08/18] WIP --- e2e/e2etests/test_bitcoin_deposit_refund.go | 10 +++++++--- e2e/runner/bitcoin.go | 10 ++++------ e2e/runner/runner.go | 1 + e2e/scripts/debug.sh | 2 +- e2e/scripts/run.sh | 4 ++-- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go index 2f976647c9..46e3287e7c 100644 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ b/e2e/e2etests/test_bitcoin_deposit_refund.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/x/crosschain/types" ) func TestDepositBTCRefund(r *runner.E2ERunner, args []string) { @@ -25,13 +26,16 @@ func TestDepositBTCRefund(r *runner.E2ERunner, args []string) { // ACT // Send a single UTXO to TSS address txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, []byte("gibberish-memo")) - require.NotEmpty(r, err) + require.NoError(r, err) // Wait for processing in zetaclient - utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) // ASSERT - // todo... + // Check that it's status is related to tx reversal + actualStatus := cctx.CctxStatus.Status + + require.Contains(r, []types.CctxStatus{types.CctxStatus_PendingRevert, types.CctxStatus_Reverted}, actualStatus) } func parseFloat(t require.TestingT, s string) float64 { diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index 493a7a3fd3..d4dc3df15c 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/pkg/chains" @@ -186,6 +187,7 @@ func (runner *E2ERunner) SendToTSSFromDeployerWithMemo( btcRPC := runner.BtcRPCClient to := runner.BTCTSSAddress btcDeployerAddress := runner.BTCDeployerAddress + require.NotNil(runner, runner.BTCDeployerAddress, "btcDeployerAddress is nil") // prepare inputs inputs := make([]btcjson.TransactionInput, len(inputUTXOs)) @@ -261,13 +263,9 @@ func (runner *E2ERunner) SendToTSSFromDeployerWithMemo( } stx, signed, err := btcRPC.SignRawTransactionWithWallet2(tx, inputsForSign) - if err != nil { - panic(err) - } + require.NoError(runner, err) + require.True(runner, signed, "btc transaction is not signed") - if !signed { - panic("btc transaction not signed") - } txid, err := btcRPC.SendRawTransaction(stx, true) if err != nil { panic(err) diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 368f517fab..8c8c82111d 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -2,6 +2,7 @@ package runner import ( "context" + "os" "sync" "time" diff --git a/e2e/scripts/debug.sh b/e2e/scripts/debug.sh index 87787b505f..bf5b83539d 100755 --- a/e2e/scripts/debug.sh +++ b/e2e/scripts/debug.sh @@ -20,7 +20,7 @@ dlv_opts="--headless --listen=:2345 --api-version=2 --accept-multiclient" # Collect additional arguments e2e_test_args=$(echo "$@" | tr ' ' ',') -e2e_opts="--config cmd/zetae2e/config/local.yml --skip-header-proof --skip-setup" +e2e_opts="--config cmd/zetae2e/config/local.yml" # Echo commands # shellcheck disable=SC2086 diff --git a/e2e/scripts/run.sh b/e2e/scripts/run.sh index 0243343dff..abce5af145 100755 --- a/e2e/scripts/run.sh +++ b/e2e/scripts/run.sh @@ -18,9 +18,9 @@ shift # Collect additional arguments e2e_test_args=$(echo "$@" | tr ' ' ',') -e2e_opts="--config cmd/zetae2e/config/local.yml --skip-header-proof --skip-setup" +e2e_opts="--config cmd/zetae2e/config/local.yml" # Echo commands # shellcheck disable=SC2086 set -x -go run ./cmd/zetae2e/ run $test:$e2e_test_args --config $e2e_opts \ No newline at end of file +go run ./cmd/zetae2e/ run $test:$e2e_test_args $e2e_opts \ No newline at end of file From 1e9506f5102e5cba6761260dd3bf669ebea9bb2e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 19 Jun 2024 12:32:50 +0200 Subject: [PATCH 09/18] Fix naming --- e2e/e2etests/e2etests.go | 6 +++--- e2e/e2etests/test_bitcoin_deposit_refund.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 345a835ccb..208f94a409 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -56,7 +56,7 @@ const ( Test transfer of Bitcoin asset across chains */ TestBitcoinDepositName = "bitcoin_deposit" - TestBitcoinDepositRefund = "bitcoin_deposit_refund" + TestBitcoinDepositRefundName = "bitcoin_deposit_refund" TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit" TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot" TestBitcoinWithdrawLegacyName = "bitcoin_withdraw_legacy" @@ -334,11 +334,11 @@ var AllE2ETests = []runner.E2ETest{ TestBitcoinDeposit, ), runner.NewE2ETest( - TestBitcoinDepositRefund, + TestBitcoinDepositRefundName, "deposit Bitcoin into ZEVM; expect refund", []runner.ArgDefinition{ {Description: "amount in btc", DefaultValue: "0.001"}, }, - TestDepositBTCRefund, + TestBitcoinDepositRefund, ), runner.NewE2ETest( TestBitcoinWithdrawSegWitName, diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go index 46e3287e7c..141ef33697 100644 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ b/e2e/e2etests/test_bitcoin_deposit_refund.go @@ -9,7 +9,7 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" ) -func TestDepositBTCRefund(r *runner.E2ERunner, args []string) { +func TestBitcoinDepositRefund(r *runner.E2ERunner, args []string) { // ARRANGE // Given amount to send require.Len(r, args, 1) From 027473225931369279ee097f4ad4aa90237e1cfb Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 19 Jun 2024 14:50:40 +0200 Subject: [PATCH 10/18] WIP [2] --- e2e/e2etests/e2etests.go | 2 +- e2e/e2etests/test_bitcoin_deposit_refund.go | 39 +++++++++-- e2e/utils/zetacore.go | 73 ++++++++++++++++++++- 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 208f94a409..d4bef685ec 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -336,7 +336,7 @@ var AllE2ETests = []runner.E2ETest{ runner.NewE2ETest( TestBitcoinDepositRefundName, "deposit Bitcoin into ZEVM; expect refund", []runner.ArgDefinition{ - {Description: "amount in btc", DefaultValue: "0.001"}, + {Description: "amount in btc", DefaultValue: "0.1"}, }, TestBitcoinDepositRefund, ), diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go index 141ef33697..79a404a9ff 100644 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ b/e2e/e2etests/test_bitcoin_deposit_refund.go @@ -1,12 +1,15 @@ package e2etests import ( + "context" "strconv" + "time" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/x/crosschain/types" + zetabitcoin "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" ) func TestBitcoinDepositRefund(r *runner.E2ERunner, args []string) { @@ -14,28 +17,54 @@ func TestBitcoinDepositRefund(r *runner.E2ERunner, args []string) { // Given amount to send require.Len(r, args, 1) amount := parseFloat(r, args[0]) + amount += zetabitcoin.DefaultDepositorFee // Given BTC address r.SetBtcAddress(r.Name, false) // Given a list of UTXOs - utxos, err := r.BtcRPCClient.ListUnspent() + utxos, err := r.ListDeployerUTXOs() require.NoError(r, err) require.NotEmpty(r, utxos) // ACT - // Send a single UTXO to TSS address + // Send BTC to TSS address with a dummy memo txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, []byte("gibberish-memo")) require.NoError(r, err) + require.NotEmpty(r, txHash) // Wait for processing in zetaclient - cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) + cctxs := utils.WaitCctxByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) // ASSERT + require.Len(r, cctxs, 1) + // Check that it's status is related to tx reversal - actualStatus := cctx.CctxStatus.Status + expectedStatuses := []string{types.CctxStatus_PendingRevert.String(), types.CctxStatus_Reverted.String()} + actualStatus := cctxs[0].CctxStatus.Status.String() + + require.Contains(r, expectedStatuses, actualStatus) + + r.Logger.Info("CCTX revert status: %s", actualStatus) + + // Now we want to make sure refund TX is completed. Let's check that zetaclient issued a refund on BTC + ctx, cancel := context.WithTimeout(r.Ctx, time.Minute*10) + defer cancel() + + searchForCrossChainWithBtcRefund := utils.Matches(func(tx types.CrossChainTx) bool { + if len(tx.OutboundParams) != 2 { + return false + } + + btcRefundTx := tx.OutboundParams[1] + + return btcRefundTx.Hash != "" + }) + + cctxs = utils.WaitCctxByInboundHash(ctx, r, txHash.String(), r.CctxClient, searchForCrossChainWithBtcRefund) + require.Len(r, cctxs, 1) - require.Contains(r, []types.CctxStatus{types.CctxStatus_PendingRevert, types.CctxStatus_Reverted}, actualStatus) + // todo check that BTC refund is completed } func parseFloat(t require.TestingT, s string) float64 { diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index 093ff4314f..0504dc1481 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -7,10 +7,14 @@ import ( rpchttp "github.com/cometbft/cometbft/rpc/client/http" coretypes "github.com/cometbft/cometbft/rpc/core/types" - + "github.com/stretchr/testify/require" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) +type CCTXClient = crosschaintypes.QueryClient + const ( FungibleAdminName = "fungibleadmin" @@ -170,6 +174,73 @@ func WaitCCTXMinedByIndex( } } +type WaitOpts func(c *waitConfig) + +// MatchStatus waits for a specific CCTX status. +func MatchStatus(s crosschaintypes.CctxStatus) WaitOpts { + return Matches(func(tx crosschaintypes.CrossChainTx) bool { + return tx.CctxStatus != nil && tx.CctxStatus.Status == s + }) +} + +func Matches(fn func(tx crosschaintypes.CrossChainTx) bool) WaitOpts { + return func(c *waitConfig) { c.matchFunction = fn } +} + +type waitConfig struct { + matchFunction func(tx crosschaintypes.CrossChainTx) bool +} + +// WaitCctxByInboundHash waits until cctx appears by inbound hash. +func WaitCctxByInboundHash(ctx context.Context, t require.TestingT, hash string, c CCTXClient, opts ...WaitOpts) []crosschaintypes.CrossChainTx { + const tick = time.Millisecond * 200 + + if _, hasDeadline := ctx.Deadline(); !hasDeadline { + var cancel func() + ctx, cancel = context.WithTimeout(ctx, DefaultCctxTimeout) + defer cancel() + } + + in := &crosschaintypes.QueryInboundHashToCctxDataRequest{InboundHash: hash} + + var cfg waitConfig + for _, opt := range opts { + opt(&cfg) + } + + matches := func(txs []crosschaintypes.CrossChainTx) bool { + if cfg.matchFunction == nil { + return true + } + + for _, tx := range txs { + if ok := cfg.matchFunction(tx); !ok { + return false + } + } + + return true + } + + for { + out, err := c.InTxHashToCctxData(ctx, in) + statusCode, _ := status.FromError(err) + + switch { + case statusCode.Code() == codes.NotFound: + // expected; let's retry + case err != nil: + require.NoError(t, err, "failed to get cctx by inbound hash: %s", hash) + case len(out.CrossChainTxs) > 0 && matches(out.CrossChainTxs): + return out.CrossChainTxs + case ctx.Err() == nil: + require.NoError(t, err, "failed to get cctx by inbound hash (ctx error): %s", hash) + } + + time.Sleep(tick) + } +} + func IsTerminalStatus(status crosschaintypes.CctxStatus) bool { return status == crosschaintypes.CctxStatus_OutboundMined || status == crosschaintypes.CctxStatus_Aborted || From b1151cea2de80e47e593386c1845b19bb8687a4c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 19 Jun 2024 14:54:19 +0200 Subject: [PATCH 11/18] Add test to CI --- cmd/zetae2e/local/local.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index c1ab0f4431..557f449636 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -278,6 +278,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestMessagePassingEVMtoZEVMRevertFailName, } bitcoinTests := []string{ + e2etests.TestBitcoinDepositName, + e2etests.TestBitcoinDepositRefundName, e2etests.TestBitcoinWithdrawSegWitName, e2etests.TestBitcoinWithdrawInvalidAddressName, e2etests.TestZetaWithdrawBTCRevertName, From fcbc85229228a96a7a2bec8ddc355de1d4c9811e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 19 Jun 2024 16:53:46 +0200 Subject: [PATCH 12/18] Complete "btc deposit refund" test case --- e2e/e2etests/test_bitcoin_deposit_refund.go | 61 ++++++++++----------- e2e/scripts/run.sh | 2 +- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go index 79a404a9ff..92cddcd74b 100644 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ b/e2e/e2etests/test_bitcoin_deposit_refund.go @@ -1,10 +1,10 @@ package e2etests import ( - "context" "strconv" - "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" @@ -14,14 +14,18 @@ import ( func TestBitcoinDepositRefund(r *runner.E2ERunner, args []string) { // ARRANGE + // Given BTC address + r.SetBtcAddress(r.Name, false) + + // Given "Live" BTC network + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + // Given amount to send require.Len(r, args, 1) amount := parseFloat(r, args[0]) amount += zetabitcoin.DefaultDepositorFee - // Given BTC address - r.SetBtcAddress(r.Name, false) - // Given a list of UTXOs utxos, err := r.ListDeployerUTXOs() require.NoError(r, err) @@ -33,38 +37,33 @@ func TestBitcoinDepositRefund(r *runner.E2ERunner, args []string) { require.NoError(r, err) require.NotEmpty(r, txHash) - // Wait for processing in zetaclient - cctxs := utils.WaitCctxByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) - // ASSERT - require.Len(r, cctxs, 1) - - // Check that it's status is related to tx reversal - expectedStatuses := []string{types.CctxStatus_PendingRevert.String(), types.CctxStatus_Reverted.String()} - actualStatus := cctxs[0].CctxStatus.Status.String() - - require.Contains(r, expectedStatuses, actualStatus) - - r.Logger.Info("CCTX revert status: %s", actualStatus) - - // Now we want to make sure refund TX is completed. Let's check that zetaclient issued a refund on BTC - ctx, cancel := context.WithTimeout(r.Ctx, time.Minute*10) - defer cancel() - + // Now we want to make sure refund TX is completed. + // Let's check that zetaclient issued a refund on BTC searchForCrossChainWithBtcRefund := utils.Matches(func(tx types.CrossChainTx) bool { - if len(tx.OutboundParams) != 2 { - return false - } - - btcRefundTx := tx.OutboundParams[1] - - return btcRefundTx.Hash != "" + return tx.GetCctxStatus().Status == types.CctxStatus_Reverted && + len(tx.OutboundParams) == 2 && + tx.OutboundParams[1].Hash != "" }) - cctxs = utils.WaitCctxByInboundHash(ctx, r, txHash.String(), r.CctxClient, searchForCrossChainWithBtcRefund) + cctxs := utils.WaitCctxByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient, searchForCrossChainWithBtcRefund) require.Len(r, cctxs, 1) - // todo check that BTC refund is completed + // Pick btc tx hash from the cctx + btcTxHash, err := chainhash.NewHashFromStr(cctxs[0].OutboundParams[1].Hash) + require.NoError(r, err) + + // Query the BTC network to check the refund transaction + refundTx, err := r.BtcRPCClient.GetTransaction(btcTxHash) + require.NoError(r, err, refundTx) + + // Finally, check the refund transaction details + refundTxDetails := refundTx.Details[0] + assert.Equal(r, "receive", refundTxDetails.Category) + assert.Equal(r, r.BTCDeployerAddress.EncodeAddress(), refundTxDetails.Address) + assert.NotEmpty(r, refundTxDetails.Amount) + + r.Logger.InfoLoud("Sent %f BTC to TSS with invalid memo, got refund of %f BTC", amount, refundTxDetails.Amount) } func parseFloat(t require.TestingT, s string) float64 { diff --git a/e2e/scripts/run.sh b/e2e/scripts/run.sh index abce5af145..abaf7bb072 100755 --- a/e2e/scripts/run.sh +++ b/e2e/scripts/run.sh @@ -18,7 +18,7 @@ shift # Collect additional arguments e2e_test_args=$(echo "$@" | tr ' ' ',') -e2e_opts="--config cmd/zetae2e/config/local.yml" +e2e_opts="--config cmd/zetae2e/config/local.yml --verbose" # Echo commands # shellcheck disable=SC2086 From 5254f39148b38ff02f2f80fd5f0bf3c9580f6f23 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 19 Jun 2024 18:09:16 +0200 Subject: [PATCH 13/18] Add WithdrawBitcoinMultipleTimes test --- changelog.md | 1 + cmd/zetae2e/local/local.go | 1 + e2e/e2etests/e2etests.go | 10 ++ .../test_bitcoin_withdraw_multiple.go | 143 ++++++------------ e2e/runner/balances.go | 20 ++- 5 files changed, 76 insertions(+), 99 deletions(-) diff --git a/changelog.md b/changelog.md index 1dfe34192d..6acae6b702 100644 --- a/changelog.md +++ b/changelog.md @@ -57,6 +57,7 @@ * [2266](https://github.com/zeta-chain/node/pull/2266) - try fixing E2E test `crosschain_swap` failure `btc transaction not signed` * [2294](https://github.com/zeta-chain/node/pull/2294) - add and fix existing ethermint rpc unit test * [2299](https://github.com/zeta-chain/node/pull/2299) - add `zetae2e` command to deploy test contracts +* [2349](https://github.com/zeta-chain/node/pull/2349) - add TestBitcoinDepositRefund and WithdrawBitcoinMultipleTimes E2E tests ### Fixes diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 557f449636..daeb19ccfe 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -288,6 +288,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { bitcoinAdvancedTests := []string{ e2etests.TestBitcoinWithdrawTaprootName, e2etests.TestBitcoinWithdrawLegacyName, + e2etests.TestBitcoinWithdrawMultipleName, e2etests.TestBitcoinWithdrawP2SHName, e2etests.TestBitcoinWithdrawP2WSHName, e2etests.TestBitcoinWithdrawRestrictedName, diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index d4bef685ec..d438d34f01 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -59,6 +59,7 @@ const ( TestBitcoinDepositRefundName = "bitcoin_deposit_refund" TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit" TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot" + TestBitcoinWithdrawMultipleName = "bitcoin_withdraw_multiple" TestBitcoinWithdrawLegacyName = "bitcoin_withdraw_legacy" TestBitcoinWithdrawP2WSHName = "bitcoin_withdraw_p2wsh" TestBitcoinWithdrawP2SHName = "bitcoin_withdraw_p2sh" @@ -367,6 +368,15 @@ var AllE2ETests = []runner.E2ETest{ }, TestBitcoinWithdrawLegacy, ), + runner.NewE2ETest( + TestBitcoinWithdrawMultipleName, + "withdraw BTC from ZEVM multiple times", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "0.01"}, + {Description: "times", DefaultValue: "2"}, + }, + WithdrawBitcoinMultipleTimes, + ), runner.NewE2ETest( TestBitcoinWithdrawP2WSHName, "withdraw BTC from ZEVM to a P2WSH address", diff --git a/e2e/e2etests/test_bitcoin_withdraw_multiple.go b/e2e/e2etests/test_bitcoin_withdraw_multiple.go index b9271b8b91..2bb812f257 100644 --- a/e2e/e2etests/test_bitcoin_withdraw_multiple.go +++ b/e2e/e2etests/test_bitcoin_withdraw_multiple.go @@ -1,96 +1,51 @@ package e2etests -// WithdrawBitcoinMultipleTimes ... -// TODO: complete and uncomment E2E test -// https://github.com/zeta-chain/node-private/issues/79 -//func WithdrawBitcoinMultipleTimes(r *runner.E2ERunner, repeat int64) { -// totalAmount := big.NewInt(int64(0.1 * 1e8)) -// -// // #nosec G701 test - always in range -// amount := big.NewInt(int64(0.1 * 1e8 / float64(repeat))) -// -// // check if the deposit is successful -// BTCZRC20Addr, err := r.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(common.BtcRegtestChain.ChainId)) -// if err != nil { -// panic(err) -// } -// r.Logger.Info("BTCZRC20Addr: %s", BTCZRC20Addr.Hex()) -// BTCZRC20, err := zrc20.NewZRC20(BTCZRC20Addr, r.ZEVMClient) -// if err != nil { -// panic(err) -// } -// balance, err := BTCZRC20.BalanceOf(&bind.CallOpts{}, r.DeployerAddress) -// if err != nil { -// panic(err) -// } -// if balance.Cmp(totalAmount) < 0 { -// panic(fmt.Errorf("not enough balance in ZRC20 contract")) -// } -// // approve the ZRC20 contract to spend 1 BTC from the deployer address -// { -// // approve more to cover withdraw fee -// tx, err := BTCZRC20.Approve(r.ZEVMAuth, BTCZRC20Addr, totalAmount.Mul(totalAmount, big.NewInt(100))) -// if err != nil { -// panic(err) -// } -// receipt := config.MustWaitForTxReceipt(r.ZEVMClient, tx, r.Logger) -// r.Logger.Info("approve receipt: status %d", receipt.Status) -// if receipt.Status != 1 { -// panic(fmt.Errorf("approve receipt status is not 1")) -// } -// } -// go func() { -// for { -// time.Sleep(3 * time.Second) -// _, err = r.GenerateToAddressIfLocalBitcoin(1, r.BTCDeployerAddress) -// if err != nil { -// panic(err) -// } -// } -// }() -// // withdraw 0.1 BTC from ZRC20 to BTC address -// for i := int64(0); i < repeat; i++ { -// _, gasFee, err := BTCZRC20.WithdrawGasFee(&bind.CallOpts{}) -// if err != nil { -// panic(err) -// } -// r.Logger.Info("withdraw gas fee: %d", gasFee) -// tx, err := BTCZRC20.Withdraw(r.ZEVMAuth, []byte(r.BTCDeployerAddress.EncodeAddress()), amount) -// if err != nil { -// panic(err) -// } -// receipt := config.MustWaitForTxReceipt(r.ZEVMClient, tx, r.Logger) -// r.Logger.Info("withdraw receipt: status %d", receipt.Status) -// if receipt.Status != 1 { -// panic(fmt.Errorf("withdraw receipt status is not 1")) -// } -// _, err = r.GenerateToAddressIfLocalBitcoin(10, r.BTCDeployerAddress) -// if err != nil { -// panic(err) -// } -// cctx := config.WaitCctxMinedByInTxHash(receipt.TxHash.Hex(), r.CctxClient, r.Logger) -// outTxHash := cctx.GetCurrentOutTxParam().OutboundTxHash -// hash, err := chainhash.NewHashFromStr(outTxHash) -// if err != nil { -// panic(err) -// } -// -// rawTx, err := r.BtcRPCClient.GetRawTransactionVerbose(hash) -// if err != nil { -// panic(err) -// } -// r.Logger.Info("raw tx:") -// r.Logger.Info(" TxIn: %d", len(rawTx.Vin)) -// for idx, txIn := range rawTx.Vin { -// r.Logger.Info(" TxIn %d:", idx) -// r.Logger.Info(" TxID:Vout: %s:%d", txIn.Txid, txIn.Vout) -// r.Logger.Info(" ScriptSig: %s", txIn.ScriptSig.Hex) -// } -// r.Logger.Info(" TxOut: %d", len(rawTx.Vout)) -// for _, txOut := range rawTx.Vout { -// r.Logger.Info(" TxOut %d:", txOut.N) -// r.Logger.Info(" Value: %.8f", txOut.Value) -// r.Logger.Info(" ScriptPubKey: %s", txOut.ScriptPubKey.Hex) -// } -// } -//} +import ( + "math/big" + "strconv" + + "github.com/btcsuite/btcutil" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/pkg/chains" +) + +const defaultReceiver = "mxpYha3UJKUgSwsAz2qYRqaDSwAkKZ3YEY" + +func WithdrawBitcoinMultipleTimes(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given amount and repeat count + require.Len(r, args, 2) + var ( + amount = btcAmountFromFloat64(r, parseFloat(r, args[0])) + times = parseInt(r, args[1]) + ) + + // Given BTC address set + r.SetBtcAddress(r.Name, false) + + // Given a receiver + receiver, err := chains.DecodeBtcAddress(defaultReceiver, r.GetBitcoinChainID()) + require.NoError(r, err) + + // ACT + for i := 0; i < times; i++ { + withdrawBTCZRC20(r, receiver, amount) + } +} + +func parseInt(t require.TestingT, s string) int { + v, err := strconv.Atoi(s) + require.NoError(t, err, "unable to parse int from %q", s) + + return v +} + +// bigIntFromFloat64 takes float64 (e.g. 0.001) that represents btc amount +// and converts it to big.Int for downstream usage. +func btcAmountFromFloat64(t require.TestingT, amount float64) *big.Int { + satoshi, err := btcutil.NewAmount(amount) + require.NoError(t, err) + + return big.NewInt(int64(satoshi)) +} diff --git a/e2e/runner/balances.go b/e2e/runner/balances.go index 19acfd1509..3e176bcb8d 100644 --- a/e2e/runner/balances.go +++ b/e2e/runner/balances.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/pkg/errors" ) // AccountBalances is a struct that contains the balances of the accounts used in the E2E test @@ -100,20 +101,29 @@ func (runner *E2ERunner) GetBitcoinBalance() (string, error) { return "", fmt.Errorf("failed to decode BTC address: %w", err) } + total, err := runner.GetBitcoinBalanceByAddress(address) + if err != nil { + return "", err + } + + return total.String(), nil +} + +// GetBitcoinBalanceByAddress get btc balance by address. +func (runner *E2ERunner) GetBitcoinBalanceByAddress(address btcutil.Address) (btcutil.Amount, error) { unspentList, err := runner.BtcRPCClient.ListUnspentMinMaxAddresses(1, 9999999, []btcutil.Address{address}) if err != nil { - return "", fmt.Errorf("failed to list unspent: %w", err) + return 0, errors.Wrap(err, "failed to list unspent") } - // calculate total amount - var totalAmount btcutil.Amount + var total btcutil.Amount for _, unspent := range unspentList { if unspent.Spendable { - totalAmount += btcutil.Amount(unspent.Amount * 1e8) + total += btcutil.Amount(unspent.Amount * 1e8) } } - return totalAmount.String(), nil + return total, nil } // PrintAccountBalances shows the account balances of the accounts used in the E2E test From b031671bc257ee5b901089c78615f4c217ad240a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 19 Jun 2024 18:16:50 +0200 Subject: [PATCH 14/18] Lint --- e2e/e2etests/helper_bitcoin.go | 7 ++++++- e2e/e2etests/test_bitcoin_deposit_refund.go | 1 + e2e/e2etests/test_bitcoin_withdraw_multiple.go | 1 + e2e/utils/zetacore.go | 11 +++++++++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/e2e/e2etests/helper_bitcoin.go b/e2e/e2etests/helper_bitcoin.go index 220b7040bd..64d779785d 100644 --- a/e2e/e2etests/helper_bitcoin.go +++ b/e2e/e2etests/helper_bitcoin.go @@ -76,7 +76,12 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) // get cctx and check status cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) - require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status, "cctx status is not OutboundMined") + require.Equal( + r, + crosschaintypes.CctxStatus_OutboundMined, + cctx.CctxStatus.Status, + "cctx status is not OutboundMined", + ) // get bitcoin tx according to the outTxHash in cctx outTxHash := cctx.GetCurrentOutboundParam().Hash diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go index 92cddcd74b..4afdea7088 100644 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ b/e2e/e2etests/test_bitcoin_deposit_refund.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/x/crosschain/types" diff --git a/e2e/e2etests/test_bitcoin_withdraw_multiple.go b/e2e/e2etests/test_bitcoin_withdraw_multiple.go index 2bb812f257..a1f201d8db 100644 --- a/e2e/e2etests/test_bitcoin_withdraw_multiple.go +++ b/e2e/e2etests/test_bitcoin_withdraw_multiple.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/pkg/chains" ) diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index 0504dc1481..26b647a53e 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -8,9 +8,10 @@ import ( rpchttp "github.com/cometbft/cometbft/rpc/client/http" coretypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/stretchr/testify/require" - crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) type CCTXClient = crosschaintypes.QueryClient @@ -192,7 +193,13 @@ type waitConfig struct { } // WaitCctxByInboundHash waits until cctx appears by inbound hash. -func WaitCctxByInboundHash(ctx context.Context, t require.TestingT, hash string, c CCTXClient, opts ...WaitOpts) []crosschaintypes.CrossChainTx { +func WaitCctxByInboundHash( + ctx context.Context, + t require.TestingT, + hash string, + c CCTXClient, + opts ...WaitOpts, +) []crosschaintypes.CrossChainTx { const tick = time.Millisecond * 200 if _, hasDeadline := ctx.Deadline(); !hasDeadline { From bda6baed4e7134b727bec71e44f389b6f69d286f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 20 Jun 2024 12:24:36 +0200 Subject: [PATCH 15/18] Revert MD formatting --- e2e/README.md | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index dc2ad442fa..fdd1c6d94e 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,33 +1,24 @@ # `e2e` -`e2e` is a comprehensive suite of E2E tests designed to validate the integration and functionality of the ZetaChain -network, particularly its interactions with Bitcoin and EVM (Ethereum Virtual Machine) networks. This tool is essential -for ensuring the robustness and reliability of ZetaChain's cross-chain functionalities. +`e2e` is a comprehensive suite of E2E tests designed to validate the integration and functionality of the ZetaChain network, particularly its interactions with Bitcoin and EVM (Ethereum Virtual Machine) networks. This tool is essential for ensuring the robustness and reliability of ZetaChain's cross-chain functionalities. ## Packages - The E2E testing project is organized into several packages, each with a specific role: -- `config`: Provides general configuration for E2E tests, including RPC addresses for connected networks, addresses of - deployed smart contracts, and account details for test transactions. +- `config`: Provides general configuration for E2E tests, including RPC addresses for connected networks, addresses of deployed smart contracts, and account details for test transactions. - `contracts`: Includes sample Solidity smart contracts used in testing scenarios. - `runner`: Responsible for executing E2E tests, handling interactions with various network clients. -- `e2etests`: Houses a collection of E2E tests that can be run against the ZetaChain network. Each test is implemented - as a separate Go file prefixed with `test_`. +- `e2etests`: Houses a collection of E2E tests that can be run against the ZetaChain network. Each test is implemented as a separate Go file prefixed with `test_`. - `txserver`: A minimalistic client for interacting with the ZetaChain RPC interface. -- `utils`: Offers utility functions to facilitate interactions with the different blockchain networks involved in - testing. +- `utils`: Offers utility functions to facilitate interactions with the different blockchain networks involved in testing. ## Config -The E2E testing suite utilizes a flexible and comprehensive configuration system defined in the config package, which is -central to setting up and customizing your test environments. The configuration is structured as follows: +The E2E testing suite utilizes a flexible and comprehensive configuration system defined in the config package, which is central to setting up and customizing your test environments. The configuration is structured as follows: -A config YAML file can be provided to the E2E test tool via the `--config` flag. If no config file is provided, the tool -will use default values for all configuration parameters. +A config YAML file can be provided to the E2E test tool via the `--config` flag. If no config file is provided, the tool will use default values for all configuration parameters. ### Config Structure - - `RPCs`: Defines the RPC endpoints for various networks involved in the testing. - `Contracts`: Specifies the addresses of pre-deployed smart contracts relevant to the tests. - `ZetaChainID`: The specific chain ID of the ZetaChain network being tested. @@ -43,7 +34,6 @@ will use default values for all configuration parameters. ### Contracts Configuration: **EVM Contracts** - - `ZetaEthAddress`: Address of Zeta token contract on EVM chain. - `ConnectorEthAddr`: Address of a connector contract on EVM chain. - `ERC20`: Address of the ERC20 token contract on EVM chain. From 23d29e53b1868a1a877e7f1fc5331a5eac2f990c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 20 Jun 2024 13:11:27 +0200 Subject: [PATCH 16/18] Address PR comments --- e2e/e2etests/helper_bitcoin.go | 111 ------------- e2e/e2etests/helper_erc20.go | 32 ---- e2e/e2etests/helpers.go | 147 ++++++++++++++++++ e2e/e2etests/test_bitcoin_deposit_refund.go | 10 +- .../test_bitcoin_withdraw_multiple.go | 20 --- e2e/scripts/debug.sh | 2 +- e2e/scripts/run.sh | 2 +- 7 files changed, 150 insertions(+), 174 deletions(-) delete mode 100644 e2e/e2etests/helper_bitcoin.go delete mode 100644 e2e/e2etests/helper_erc20.go create mode 100644 e2e/e2etests/helpers.go diff --git a/e2e/e2etests/helper_bitcoin.go b/e2e/e2etests/helper_bitcoin.go deleted file mode 100644 index 64d779785d..0000000000 --- a/e2e/e2etests/helper_bitcoin.go +++ /dev/null @@ -1,111 +0,0 @@ -package e2etests - -import ( - "math/big" - "strconv" - - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" - "github.com/stretchr/testify/require" - - "github.com/zeta-chain/zetacore/e2e/runner" - "github.com/zeta-chain/zetacore/e2e/utils" - "github.com/zeta-chain/zetacore/pkg/chains" - crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -func parseBitcoinWithdrawArgs(r *runner.E2ERunner, args []string, defaultReceiver string) (btcutil.Address, *big.Int) { - // get bitcoin chain id - chainID := r.GetBitcoinChainID() - - // parse receiver address - var err error - var receiver btcutil.Address - if args[0] == "" { - // use the default receiver - receiver, err = chains.DecodeBtcAddress(defaultReceiver, chainID) - if err != nil { - panic("Invalid default receiver address specified for TestBitcoinWithdraw.") - } - } else { - receiver, err = chains.DecodeBtcAddress(args[0], chainID) - if err != nil { - panic("Invalid receiver address specified for TestBitcoinWithdraw.") - } - } - - // parse the withdrawal amount - withdrawalAmount, err := strconv.ParseFloat(args[1], 64) - if err != nil { - panic("Invalid withdrawal amount specified for TestBitcoinWithdraw.") - } - withdrawalAmountSat, err := btcutil.NewAmount(withdrawalAmount) - if err != nil { - panic(err) - } - amount := big.NewInt(int64(withdrawalAmountSat)) - - return receiver, amount -} - -func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) *btcjson.TxRawResult { - tx, err := r.BTCZRC20.Approve( - r.ZEVMAuth, - r.BTCZRC20Addr, - big.NewInt(amount.Int64()*2), - ) // approve more to cover withdraw fee - require.NoError(r, err) - - receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - require.Equal(r, uint64(1), receipt.Status, "approve receipt status is not 1") - - // mine blocks if testing on regnet - stop := r.MineBlocksIfLocalBitcoin() - - // withdraw 'amount' of BTC from ZRC20 to BTC address - tx, err = r.BTCZRC20.Withdraw(r.ZEVMAuth, []byte(to.EncodeAddress()), amount) - require.NoError(r, err) - - receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - require.Equal(r, uint64(1), receipt.Status, "withdraw receipt status is not 1") - - // mine 10 blocks to confirm the withdrawal tx - _, err = r.GenerateToAddressIfLocalBitcoin(10, to) - require.NoError(r, err) - - // get cctx and check status - cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) - require.Equal( - r, - crosschaintypes.CctxStatus_OutboundMined, - cctx.CctxStatus.Status, - "cctx status is not OutboundMined", - ) - - // get bitcoin tx according to the outTxHash in cctx - outTxHash := cctx.GetCurrentOutboundParam().Hash - hash, err := chainhash.NewHashFromStr(outTxHash) - require.NoError(r, err) - - rawTx, err := r.BtcRPCClient.GetRawTransactionVerbose(hash) - require.NoError(r, err) - r.Logger.Info("raw tx:") - r.Logger.Info(" TxIn: %d", len(rawTx.Vin)) - for idx, txIn := range rawTx.Vin { - r.Logger.Info(" TxIn %d:", idx) - r.Logger.Info(" TxID:Vout: %s:%d", txIn.Txid, txIn.Vout) - r.Logger.Info(" ScriptSig: %s", txIn.ScriptSig.Hex) - } - r.Logger.Info(" TxOut: %d", len(rawTx.Vout)) - for _, txOut := range rawTx.Vout { - r.Logger.Info(" TxOut %d:", txOut.N) - r.Logger.Info(" Value: %.8f", txOut.Value) - r.Logger.Info(" ScriptPubKey: %s", txOut.ScriptPubKey.Hex) - } - - // stop mining - stop() - - return rawTx -} diff --git a/e2e/e2etests/helper_erc20.go b/e2e/e2etests/helper_erc20.go deleted file mode 100644 index fd4630067b..0000000000 --- a/e2e/e2etests/helper_erc20.go +++ /dev/null @@ -1,32 +0,0 @@ -package e2etests - -import ( - ethcommon "github.com/ethereum/go-ethereum/common" - - "github.com/zeta-chain/zetacore/e2e/runner" - crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -// verifyTransferAmountFromCCTX verifies the transfer amount from the CCTX on EVM -func verifyTransferAmountFromCCTX(r *runner.E2ERunner, cctx *crosschaintypes.CrossChainTx, amount int64) { - r.Logger.Info("outTx hash %s", cctx.GetCurrentOutboundParam().Hash) - - receipt, err := r.EVMClient.TransactionReceipt( - r.Ctx, - ethcommon.HexToHash(cctx.GetCurrentOutboundParam().Hash), - ) - if err != nil { - panic(err) - } - r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) - for _, log := range receipt.Logs { - event, err := r.ERC20.ParseTransfer(*log) - if err != nil { - continue - } - r.Logger.Info(" logs: from %s, to %s, value %d", event.From.Hex(), event.To.Hex(), event.Value) - if event.Value.Int64() != amount { - panic("value is not correct") - } - } -} diff --git a/e2e/e2etests/helpers.go b/e2e/e2etests/helpers.go new file mode 100644 index 0000000000..a252389fec --- /dev/null +++ b/e2e/e2etests/helpers.go @@ -0,0 +1,147 @@ +package e2etests + +import ( + "math/big" + "strconv" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcutil" + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) *btcjson.TxRawResult { + tx, err := r.BTCZRC20.Approve( + r.ZEVMAuth, + r.BTCZRC20Addr, + big.NewInt(amount.Int64()*2), + ) // approve more to cover withdraw fee + require.NoError(r, err) + + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + receiptApproved(r, receipt) + + // mine blocks if testing on regnet + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // withdraw 'amount' of BTC from ZRC20 to BTC address + tx, err = r.BTCZRC20.Withdraw(r.ZEVMAuth, []byte(to.EncodeAddress()), amount) + require.NoError(r, err) + + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + receiptApproved(r, receipt) + + // mine 10 blocks to confirm the withdrawal tx + _, err = r.GenerateToAddressIfLocalBitcoin(10, to) + require.NoError(r, err) + + // get cctx and check status + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + cctxStatusEquals(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + + // get bitcoin tx according to the outTxHash in cctx + outTxHash := cctx.GetCurrentOutboundParam().Hash + hash, err := chainhash.NewHashFromStr(outTxHash) + require.NoError(r, err) + + rawTx, err := r.BtcRPCClient.GetRawTransactionVerbose(hash) + require.NoError(r, err) + + r.Logger.Info("raw tx:") + r.Logger.Info(" TxIn: %d", len(rawTx.Vin)) + for idx, txIn := range rawTx.Vin { + r.Logger.Info(" TxIn %d:", idx) + r.Logger.Info(" TxID:Vout: %s:%d", txIn.Txid, txIn.Vout) + r.Logger.Info(" ScriptSig: %s", txIn.ScriptSig.Hex) + } + r.Logger.Info(" TxOut: %d", len(rawTx.Vout)) + for _, txOut := range rawTx.Vout { + r.Logger.Info(" TxOut %d:", txOut.N) + r.Logger.Info(" Value: %.8f", txOut.Value) + r.Logger.Info(" ScriptPubKey: %s", txOut.ScriptPubKey.Hex) + } + + return rawTx +} + +// verifyTransferAmountFromCCTX verifies the transfer amount from the CCTX on EVM +func verifyTransferAmountFromCCTX(r *runner.E2ERunner, cctx *crosschaintypes.CrossChainTx, amount int64) { + r.Logger.Info("outTx hash %s", cctx.GetCurrentOutboundParam().Hash) + + receipt, err := r.EVMClient.TransactionReceipt( + r.Ctx, + ethcommon.HexToHash(cctx.GetCurrentOutboundParam().Hash), + ) + require.NoError(r, err) + + r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) + + for _, log := range receipt.Logs { + event, err := r.ERC20.ParseTransfer(*log) + if err != nil { + continue + } + r.Logger.Info(" logs: from %s, to %s, value %d", event.From.Hex(), event.To.Hex(), event.Value) + require.Equal(r, amount, event.Value.Int64(), "value is not correct") + } +} + +// Parse helpers ==========================================> + +func parseFloat(t require.TestingT, s string) float64 { + f, err := strconv.ParseFloat(s, 64) + require.NoError(t, err, "unable to parse float %q", s) + return f +} + +func parseInt(t require.TestingT, s string) int { + v, err := strconv.Atoi(s) + require.NoError(t, err, "unable to parse int from %q", s) + + return v +} + +// bigIntFromFloat64 takes float64 (e.g. 0.001) that represents btc amount +// and converts it to big.Int for downstream usage. +func btcAmountFromFloat64(t require.TestingT, amount float64) *big.Int { + satoshi, err := btcutil.NewAmount(amount) + require.NoError(t, err) + + return big.NewInt(int64(satoshi)) +} + +func parseBitcoinWithdrawArgs(r *runner.E2ERunner, args []string, defaultReceiver string) (btcutil.Address, *big.Int) { + require.NotEmpty(r, args, "args list is empty") + + receiverRaw := defaultReceiver + if args[0] != "" { + receiverRaw = args[0] + } + + receiver, err := chains.DecodeBtcAddress(receiverRaw, r.GetBitcoinChainID()) + require.NoError(r, err, "unable to decode btc address") + + withdrawalAmount := parseFloat(r, args[1]) + amount := btcAmountFromFloat64(r, withdrawalAmount) + + return receiver, amount +} + +// Testify aliases ==========================================> + +func receiptApproved(t require.TestingT, receipt *ethtypes.Receipt) { + require.Equal(t, ethtypes.ReceiptStatusSuccessful, receipt.Status, "receipt status is not successful") +} + +func cctxStatusEquals(t require.TestingT, cctx *crosschaintypes.CrossChainTx, expected crosschaintypes.CctxStatus) { + require.NotNil(t, cctx.CctxStatus) + require.Equal(t, expected, cctx.CctxStatus.Status, "cctx status is not %q", expected.String()) +} diff --git a/e2e/e2etests/test_bitcoin_deposit_refund.go b/e2e/e2etests/test_bitcoin_deposit_refund.go index 4afdea7088..c17f04ba5d 100644 --- a/e2e/e2etests/test_bitcoin_deposit_refund.go +++ b/e2e/e2etests/test_bitcoin_deposit_refund.go @@ -1,8 +1,6 @@ package e2etests import ( - "strconv" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -64,11 +62,5 @@ func TestBitcoinDepositRefund(r *runner.E2ERunner, args []string) { assert.Equal(r, r.BTCDeployerAddress.EncodeAddress(), refundTxDetails.Address) assert.NotEmpty(r, refundTxDetails.Amount) - r.Logger.InfoLoud("Sent %f BTC to TSS with invalid memo, got refund of %f BTC", amount, refundTxDetails.Amount) -} - -func parseFloat(t require.TestingT, s string) float64 { - f, err := strconv.ParseFloat(s, 64) - require.NoError(t, err, "unable to parse float %q", s) - return f + r.Logger.Info("Sent %f BTC to TSS with invalid memo, got refund of %f BTC", amount, refundTxDetails.Amount) } diff --git a/e2e/e2etests/test_bitcoin_withdraw_multiple.go b/e2e/e2etests/test_bitcoin_withdraw_multiple.go index a1f201d8db..e744bbe8c8 100644 --- a/e2e/e2etests/test_bitcoin_withdraw_multiple.go +++ b/e2e/e2etests/test_bitcoin_withdraw_multiple.go @@ -1,10 +1,6 @@ package e2etests import ( - "math/big" - "strconv" - - "github.com/btcsuite/btcutil" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -34,19 +30,3 @@ func WithdrawBitcoinMultipleTimes(r *runner.E2ERunner, args []string) { withdrawBTCZRC20(r, receiver, amount) } } - -func parseInt(t require.TestingT, s string) int { - v, err := strconv.Atoi(s) - require.NoError(t, err, "unable to parse int from %q", s) - - return v -} - -// bigIntFromFloat64 takes float64 (e.g. 0.001) that represents btc amount -// and converts it to big.Int for downstream usage. -func btcAmountFromFloat64(t require.TestingT, amount float64) *big.Int { - satoshi, err := btcutil.NewAmount(amount) - require.NoError(t, err) - - return big.NewInt(int64(satoshi)) -} diff --git a/e2e/scripts/debug.sh b/e2e/scripts/debug.sh index bf5b83539d..bd520a01ee 100755 --- a/e2e/scripts/debug.sh +++ b/e2e/scripts/debug.sh @@ -25,4 +25,4 @@ e2e_opts="--config cmd/zetae2e/config/local.yml" # Echo commands # shellcheck disable=SC2086 set -x -dlv debug ./cmd/zetae2e/ $dlv_opts -- run $test:$e2e_test_args $e2e_opts \ No newline at end of file +dlv debug ./cmd/zetae2e/ $dlv_opts -- run $test:$e2e_test_args $e2e_opts diff --git a/e2e/scripts/run.sh b/e2e/scripts/run.sh index abaf7bb072..8625f41261 100755 --- a/e2e/scripts/run.sh +++ b/e2e/scripts/run.sh @@ -23,4 +23,4 @@ e2e_opts="--config cmd/zetae2e/config/local.yml --verbose" # Echo commands # shellcheck disable=SC2086 set -x -go run ./cmd/zetae2e/ run $test:$e2e_test_args $e2e_opts \ No newline at end of file +go run ./cmd/zetae2e/ run $test:$e2e_test_args $e2e_opts From ab403af1c115c96526ae048b2ef5bdb66cfcd2ee Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 20 Jun 2024 13:35:09 +0200 Subject: [PATCH 17/18] Lint --- e2e/e2etests/helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/e2etests/helpers.go b/e2e/e2etests/helpers.go index a252389fec..92386ea40f 100644 --- a/e2e/e2etests/helpers.go +++ b/e2e/e2etests/helpers.go @@ -10,10 +10,10 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/pkg/chains" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) From d956db437f0a4e3f1d55782fa8bb3972ce05a5c2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 20 Jun 2024 16:25:59 +0200 Subject: [PATCH 18/18] Address PR comments [2] --- e2e/README.md | 5 +++++ e2e/e2etests/helpers.go | 10 +++++----- e2e/scripts/debug.sh | 8 ++++++-- e2e/utils/zetacore.go | 2 ++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index fdd1c6d94e..ee7c0ee1ed 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -71,3 +71,8 @@ It's possible to debug a single test using Delve debugger. Example: `./e2e/scripts/debug.sh bitcoin_withdraw_restricted 0.001` 5. Place a breakpoint in the code. 6. Go to the editor's debug panel and hit "Debug" button. + +You can also run an alias of `zetae2e run` like so: +```shell + `./e2e/scripts/run.sh bitcoin_withdraw_restricted 0.001` +``` diff --git a/e2e/e2etests/helpers.go b/e2e/e2etests/helpers.go index 92386ea40f..5980679b63 100644 --- a/e2e/e2etests/helpers.go +++ b/e2e/e2etests/helpers.go @@ -26,7 +26,7 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) require.NoError(r, err) receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - receiptApproved(r, receipt) + requireReceiptApproved(r, receipt) // mine blocks if testing on regnet stop := r.MineBlocksIfLocalBitcoin() @@ -37,7 +37,7 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - receiptApproved(r, receipt) + requireReceiptApproved(r, receipt) // mine 10 blocks to confirm the withdrawal tx _, err = r.GenerateToAddressIfLocalBitcoin(10, to) @@ -45,7 +45,7 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) // get cctx and check status cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) - cctxStatusEquals(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + requireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) // get bitcoin tx according to the outTxHash in cctx outTxHash := cctx.GetCurrentOutboundParam().Hash @@ -137,11 +137,11 @@ func parseBitcoinWithdrawArgs(r *runner.E2ERunner, args []string, defaultReceive // Testify aliases ==========================================> -func receiptApproved(t require.TestingT, receipt *ethtypes.Receipt) { +func requireReceiptApproved(t require.TestingT, receipt *ethtypes.Receipt) { require.Equal(t, ethtypes.ReceiptStatusSuccessful, receipt.Status, "receipt status is not successful") } -func cctxStatusEquals(t require.TestingT, cctx *crosschaintypes.CrossChainTx, expected crosschaintypes.CctxStatus) { +func requireCCTXStatus(t require.TestingT, cctx *crosschaintypes.CrossChainTx, expected crosschaintypes.CctxStatus) { require.NotNil(t, cctx.CctxStatus) require.Equal(t, expected, cctx.CctxStatus.Status, "cctx status is not %q", expected.String()) } diff --git a/e2e/scripts/debug.sh b/e2e/scripts/debug.sh index bd520a01ee..9269d3ce4d 100755 --- a/e2e/scripts/debug.sh +++ b/e2e/scripts/debug.sh @@ -3,8 +3,12 @@ # Exit on any error set -e -# Make sure that dlv is installed! -# go install github.com/go-delve/delve/cmd/dlv@latest +# Check if dlv is installed +if ! command -v dlv &> /dev/null +then + echo "dlv could not be found, installing..." + go install github.com/go-delve/delve/cmd/dlv@latest +fi # Check if at least one argument is provided if [ "$#" -lt 1 ]; then diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index 26b647a53e..a641ea9518 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -184,6 +184,8 @@ func MatchStatus(s crosschaintypes.CctxStatus) WaitOpts { }) } +// Matches adds a filter to WaitCctxByInboundHash that checks cctxs match provided callback. +// ALL cctxs should match this filter. func Matches(fn func(tx crosschaintypes.CrossChainTx) bool) WaitOpts { return func(c *waitConfig) { c.matchFunction = fn } }