diff --git a/.env.testnet b/.env.testnet index 51b099ddd..fe6a3e241 100644 --- a/.env.testnet +++ b/.env.testnet @@ -1,5 +1,5 @@ # subnet A (Amplify) -subnet_a_url=https://subnets.avax.network/amplify/testnet/rpc +subnet_a_rpc_url=https://subnets.avax.network/amplify/testnet/rpc subnet_a_subnet_id=2PsShLjrFFwR51DMcAh8pyuwzLn1Ym3zRhuXLTmLCR1STk2mL6 subnet_a_chain_id=2nFUad4Nw4pCgEF6MwYgGuKrzKbHJzM8wF29jeVUL41RWHgNRa subnet_a_chain_id_hex=ea70d815f0232f5419dabafe36c964ffe5c22d17ac367b60b556ab3e17a36458 @@ -7,7 +7,7 @@ subnet_a_erc20_bridge_contract=0x297C4dCBB51839caEBB550C8387a52b4F3676d35 subnet_a_native_erc20_contract=0x2010D09052e5D3d0F2E80f62b7FB2E564e83B865 # subnet B (Bulletin) -subnet_b_url=https://subnets.avax.network/bulletin/testnet/rpc +subnet_b_rpc_url=https://subnets.avax.network/bulletin/testnet/rpc subnet_b_subnet_id=cbXsFGWSDWUYTmRXUoCirVDdQkZmUWrkQQYoVc2wUoDm8eFup subnet_b_chain_id=2e3RJ3ub9Pceh8fJ3HX3gZ6nSXJLvBJ9WoXLcU4nwdpZ8X2RLq subnet_b_chain_id_hex=d7cdc6f08b167595d1577e24838113a88b1005b471a6c430d79c48b4c89cfc53 @@ -15,7 +15,7 @@ subnet_b_erc20_bridge_contract=0x48Db0f37e1Bfb569b420aEf96bcaaE9b889Bd2E9 subnet_b_bridge_token_contract=0x3C77573dF123f287470BC463835CE6dDc60d5eeD # subnet C (Conduit) -subnet_c_url=https://subnets.avax.network/conduit/testnet/rpc +subnet_c_rpc_url=https://subnets.avax.network/conduit/testnet/rpc subnet_c_subnet_id=wW7JVmjXp8SKrpacGzM81RBXdfcLDVY6M2DkFyArEXgtkyozK subnet_c_chain_id=9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75 subnet_c_chain_id_hex=137daedb40251e7c196fd9711f6cd4a787d154cdc59fb7777d4d079cc116e5f1 diff --git a/.gitignore b/.gitignore index 4aa756357..ee3f7bfc1 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,7 @@ docker-compose-run-local.yml # Ginkgo main.log server.log -tests.test +*.test # Forge documentation contracts/docs/ diff --git a/README.md b/README.md index 3efdc6940..7d5a98d3c 100644 --- a/README.md +++ b/README.md @@ -103,21 +103,14 @@ Then run the following command from the root of the repository: ### Run the E2E tests on another network -The E2E tests can be run on another network by implementing the `Network` interface in [`package network`](./tests/network/network.go). For example, the type `FujiNetwork` in [`example_fuji_network.go`](./tests/network/example_fuji_network.go) implements this interface, pointing to the [Amplify](https://subnets-test.avax.network/amplify), [Bulletin](https://subnets-test.avax.network/bulletin), and [Conduit](https://subnets-test.avax.network/conduit) Fuji subnets. After implementing this interface, you can run the E2E tests on this network by running a program such as: -```go -func main() { - // Register a failure handler that panics - gomega.RegisterFailHandler(func(message string, callerSkip ...int) { - panic(message) - }) - - // Run the test, composing it with the Network implementation - network := network.NewFujiNetwork() - defer network.CloseNetworkConnections() - tests.BasicOneWaySend(network) -} +The same E2E test flows can be executed against external network by setting the proper environment variables in `.env.testnet` and `.env`, and running the following commands: +```bash +cp .env.example .env # Set proper values after copying. +./scripts/testnet/run_testnet_e2e_flows.sh ``` +The user wallet set in `.env` must have native tokens for each of the subnets used in order for the test flows to be able to send transactions on those networks. + ## ABI Bindings To generate Golang ABI bindings for the Solidity smart contracts, run: diff --git a/docker/run_setup.sh b/docker/run_setup.sh index 3dc4350fa..417f4d226 100755 --- a/docker/run_setup.sh +++ b/docker/run_setup.sh @@ -81,10 +81,14 @@ if [ ! -e $dir_prefix/NETWORK_RUNNING ]; then export PATH="$PATH:$HOME/.foundry/bin" - subnet_a_url="http://127.0.0.1:9650/ext/bc/$subnet_a_chain_id/rpc" - subnet_b_url="http://127.0.0.1:9650/ext/bc/$subnet_b_chain_id/rpc" - subnet_c_url="http://127.0.0.1:9650/ext/bc/$subnet_c_chain_id/rpc" - c_chain_url="http://127.0.0.1:9650/ext/bc/C/rpc" + subnet_a_rpc_url="http://127.0.0.1:9650/ext/bc/$subnet_a_chain_id/rpc" + subnet_a_ws_url="ws://127.0.0.1:9650/ext/bc/$subnet_a_chain_id/ws" + subnet_b_rpc_url="http://127.0.0.1:9650/ext/bc/$subnet_b_chain_id/rpc" + subnet_b_ws_url="ws://127.0.0.1:9650/ext/bc/$subnet_b_chain_id/ws" + subnet_c_rpc_url="http://127.0.0.1:9650/ext/bc/$subnet_c_chain_id/rpc" + subnet_c_ws_url="ws://127.0.0.1:9650/ext/bc/$subnet_c_chain_id/ws" + c_chain_rpc_url="http://127.0.0.1:9650/ext/bc/C/rpc" + c_chain_ws_url="ws://127.0.0.1:9650/ext/bc/C/ws" # Deploy TeleporterMessenger contract to each chain. cd contracts @@ -97,32 +101,32 @@ if [ ! -e $dir_prefix/NETWORK_RUNNING ]; then echo $teleporter_deploy_address $teleporter_contract_address echo "Finished reading universal deploy address and transaction" - cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $subnet_a_url - cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $subnet_b_url - cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $subnet_c_url - cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $c_chain_url + cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $subnet_a_rpc_url + cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $subnet_b_rpc_url + cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $subnet_c_rpc_url + cast send --private-key $user_private_key --value 50ether $teleporter_deploy_address --rpc-url $c_chain_rpc_url echo "Sent ether to teleporter deployer on each subnet." # Verify that the transaction status was successful for the deployments - status=$(cast publish --rpc-url $subnet_a_url $teleporter_deploy_tx | getJsonVal "['status']") + status=$(cast publish --rpc-url $subnet_a_rpc_url $teleporter_deploy_tx | getJsonVal "['status']") if [[ $status != "0x1" ]]; then echo "Error deploying Teleporter Messenger on subnet A." exit 1 fi echo "Deployed TeleporterMessenger to Subnet A." - status=$(cast publish --rpc-url $subnet_b_url $teleporter_deploy_tx | getJsonVal "['status']") + status=$(cast publish --rpc-url $subnet_b_rpc_url $teleporter_deploy_tx | getJsonVal "['status']") if [[ $status != "0x1" ]]; then echo "Error deploying Teleporter Messenger on subnet B." exit 1 fi echo "Deployed TeleporterMessenger to Subnet B." - status=$(cast publish --rpc-url $subnet_c_url $teleporter_deploy_tx | getJsonVal "['status']") + status=$(cast publish --rpc-url $subnet_c_rpc_url $teleporter_deploy_tx | getJsonVal "['status']") if [[ $status != "0x1" ]]; then echo "Error deploying Teleporter Messenger on subnet C." exit 1 fi echo "Deployed TeleporterMessenger to Subnet C." - status=$(cast publish --rpc-url $c_chain_url $teleporter_deploy_tx | getJsonVal "['status']") + status=$(cast publish --rpc-url $c_chain_rpc_url $teleporter_deploy_tx | getJsonVal "['status']") if [[ $status != "0x1" ]]; then echo "Error deploying Teleporter Messenger on C-chain." exit 1 @@ -132,29 +136,29 @@ if [ ! -e $dir_prefix/NETWORK_RUNNING ]; then # Deploy TeleporterRegistry to each chain. cd contracts registry_deploy_result_a=$(forge create --private-key $user_private_key \ - --rpc-url $subnet_a_url src/Teleporter/upgrades/TeleporterRegistry.sol:TeleporterRegistry --constructor-args "[(1,$teleporter_contract_address)]") - registry_address_a=$(parseContractAddress "$registry_deploy_result_a") - echo "TeleporterRegistry contract deployed to subnet A at $registry_address_a." + --rpc-url $subnet_a_rpc_url src/Teleporter/upgrades/TeleporterRegistry.sol:TeleporterRegistry --constructor-args "[(1,$teleporter_contract_address)]") + subnet_a_teleporter_registry_address=$(parseContractAddress "$registry_deploy_result_a") + echo "TeleporterRegistry contract deployed to subnet A at $subnet_a_teleporter_registry_address." registry_deploy_result_b=$(forge create --private-key $user_private_key \ - --rpc-url $subnet_b_url src/Teleporter/upgrades/TeleporterRegistry.sol:TeleporterRegistry --constructor-args "[(1,$teleporter_contract_address)]") - registry_address_b=$(parseContractAddress "$registry_deploy_result_b") - echo "TeleporterRegistry contract deployed to subnet B at $registry_address_b." + --rpc-url $subnet_b_rpc_url src/Teleporter/upgrades/TeleporterRegistry.sol:TeleporterRegistry --constructor-args "[(1,$teleporter_contract_address)]") + subnet_b_teleporter_registry_address=$(parseContractAddress "$registry_deploy_result_b") + echo "TeleporterRegistry contract deployed to subnet B at $subnet_b_teleporter_registry_address." registry_deploy_result_c=$(forge create --private-key $user_private_key \ - --rpc-url $subnet_c_url src/Teleporter/upgrades/TeleporterRegistry.sol:TeleporterRegistry --constructor-args "[(1,$teleporter_contract_address)]") - registry_address_c=$(parseContractAddress "$registry_deploy_result_c") - echo "TeleporterRegistry contract deployed to subnet C at $registry_address_c." + --rpc-url $subnet_c_rpc_url src/Teleporter/upgrades/TeleporterRegistry.sol:TeleporterRegistry --constructor-args "[(1,$teleporter_contract_address)]") + subnet_c_teleporter_registry_address=$(parseContractAddress "$registry_deploy_result_c") + echo "TeleporterRegistry contract deployed to subnet C at $subnet_c_teleporter_registry_address." cd .. # Send tokens to cover gas costs for the relayers. relayer_private_key=C2CE4E001B7585F543982A01FBC537CFF261A672FA8BD1FAFC08A207098FE2DE relayer_address=0xA100fF48a37cab9f87c8b5Da933DA46ea1a5fb80 - cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $subnet_a_url - cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $subnet_b_url - cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $subnet_c_url - cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $c_chain_url + cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $subnet_a_rpc_url + cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $subnet_b_rpc_url + cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $subnet_c_rpc_url + cast send --private-key $user_private_key --value 500ether $relayer_address --rpc-url $c_chain_rpc_url echo "Sent ether to relayer account on each subnet." subnet_a_chain_id_hex=$(getBlockchainIDHex $subnet_a_chain_id) diff --git a/scripts/local/e2e_test.sh b/scripts/local/e2e_test.sh index 9ee70ad1c..360ebc38f 100755 --- a/scripts/local/e2e_test.sh +++ b/scripts/local/e2e_test.sh @@ -37,11 +37,11 @@ cd $cwd # to install the ginkgo binary (required for test build and run) go install -v github.com/onsi/ginkgo/v2/ginkgo@${GINKGO_VERSION} -ginkgo build ./tests/ +ginkgo build ./tests/local/ # Run the tests echo "Running e2e tests $RUN_E2E" -RUN_E2E=true ./tests/tests.test \ +RUN_E2E=true ./tests/local/local.test \ --ginkgo.vv \ --ginkgo.label-filter=${GINKGO_LABEL_FILTER:-""} \ --ginkgo.trace diff --git a/scripts/testnet/run_testnet_e2e_flows.sh b/scripts/testnet/run_testnet_e2e_flows.sh new file mode 100755 index 000000000..a9042c79d --- /dev/null +++ b/scripts/testnet/run_testnet_e2e_flows.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -e + +TELEPORTER_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd ../.. && pwd +) + +set -a +source $TELEPORTER_PATH/.env +source $TELEPORTER_PATH/.env.testnet +go run tests/testnet/main/run_testnet_flows.go \ No newline at end of file diff --git a/tests/deliver_to_wrong_chain.go b/tests/deliver_to_wrong_chain.go deleted file mode 100644 index 1244b2734..000000000 --- a/tests/deliver_to_wrong_chain.go +++ /dev/null @@ -1,79 +0,0 @@ -package tests - -import ( - "context" - "math/big" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" - "github.com/ava-labs/teleporter/tests/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - . "github.com/onsi/gomega" -) - -func DeliverToWrongChain(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] - fundedAddress, fundedKey := network.GetFundedAccountInfo() - - // - // Get the expected teleporter message ID for Subnet B - // - teleporterMessageID, err := - subnetAInfo.TeleporterMessenger.GetNextMessageID(&bind.CallOpts{}, subnetBInfo.BlockchainID) - Expect(err).Should(BeNil()) - - // - // Send a transaction to Subnet A to issue a Warp Message from the Teleporter contract to a chain other than Subnet B - // - ctx := context.Background() - - destinationAddressKey, err := crypto.GenerateKey() - Expect(err).Should(BeNil()) - destinationAddress := crypto.PubkeyToAddress(destinationAddressKey.PublicKey) - - sendCrossChainMessageInput := teleportermessenger.TeleporterMessageInput{ - DestinationBlockchainID: ids.Empty, // Some other chain ID - DestinationAddress: destinationAddress, - FeeInfo: teleportermessenger.TeleporterFeeInfo{ - FeeTokenAddress: fundedAddress, - Amount: big.NewInt(0), - }, - RequiredGasLimit: big.NewInt(1), - AllowedRelayerAddresses: []common.Address{}, - Message: []byte{1, 2, 3, 4}, - } - - log.Info( - "Sending Teleporter transaction on source chain", - "destinationChainID", subnetBInfo.BlockchainID, - ) - - receipt, _ := utils.SendCrossChainMessageAndWaitForAcceptance( - ctx, - subnetAInfo, - subnetBInfo, - sendCrossChainMessageInput, - fundedKey, - ) - - // - // Relay the message to the destination - // - network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, false) - - // - // Check that the message was not received on the Subnet B - // - delivered, err := subnetBInfo.TeleporterMessenger.MessageReceived( - &bind.CallOpts{}, subnetAInfo.BlockchainID, teleporterMessageID, - ) - Expect(err).Should(BeNil()) - Expect(delivered).Should(BeFalse()) -} diff --git a/tests/add_fee_amount.go b/tests/flows/add_fee_amount.go similarity index 61% rename from tests/add_fee_amount.go rename to tests/flows/add_fee_amount.go index d1c83eb73..303d378c3 100644 --- a/tests/add_fee_amount.go +++ b/tests/flows/add_fee_amount.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -6,29 +6,25 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" "github.com/ethereum/go-ethereum/common" . "github.com/onsi/gomega" ) -func AddFeeAmount(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func AddFeeAmount(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) teleporterContractAddress := network.GetTeleporterContractAddress() fundedAddress, fundedKey := network.GetFundedAccountInfo() ctx := context.Background() // Use mock token as the fee token - mockTokenAddress, mockToken := localUtils.DeployExampleERC20( + mockTokenAddress, mockToken := utils.DeployExampleERC20( context.Background(), fundedKey, subnetAInfo, ) - localUtils.ExampleERC20Approve( + utils.ERC20Approve( ctx, mockToken, teleporterContractAddress, @@ -38,7 +34,7 @@ func AddFeeAmount(network network.Network) { ) initFeeAmount := big.NewInt(1) - // Send a transaction to Subnet A to issue a Warp Message from the Teleporter contract to Subnet B + // Send a transaction to SubnetA to issue a Warp Message from the Teleporter contract to SubnetB sendCrossChainMessageInput := teleportermessenger.TeleporterMessageInput{ DestinationBlockchainID: subnetBInfo.BlockchainID, DestinationAddress: fundedAddress, @@ -68,14 +64,25 @@ func AddFeeAmount(network network.Network) { ) // Relay message from SubnetA to SubnetB - network.RelayMessage(ctx, sendCrossChainMsgReceipt, subnetAInfo, subnetBInfo, true) + deliveryReceipt := network.RelayMessage(ctx, sendCrossChainMsgReceipt, subnetAInfo, subnetBInfo, true) + receiveEvent, err := utils.GetEventFromLogs( + deliveryReceipt.Logs, + subnetBInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) - // Check Teleporter message received on the destination + // Check Teleporter message received on the destination (SubnetB) delivered, err := subnetBInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetAInfo.BlockchainID, messageID) Expect(err).Should(BeNil()) Expect(delivered).Should(BeTrue()) + // Check the initial relayer reward amount on SubnetA. + initialRewardAmount, err := subnetAInfo.TeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent.RewardRedeemer, + mockTokenAddress) + Expect(err).Should(BeNil()) + // Send message from SubnetB to SubnetA. This will include the receipt for the previous message from A->B sendCrossChainMessageInput.DestinationBlockchainID = subnetAInfo.BlockchainID sendCrossChainMessageInput.FeeInfo.Amount = big.NewInt(0) @@ -91,13 +98,19 @@ func AddFeeAmount(network network.Network) { Expect(err).Should(BeNil()) Expect(delivered).Should(BeTrue()) - // Check the relayer reward amount - amount, err := - subnetAInfo.TeleporterMessenger.CheckRelayerRewardAmount(&bind.CallOpts{}, fundedAddress, mockTokenAddress) + // Check the updated relayer reward amount + expectedIncrease := new(big.Int).Add(initFeeAmount, additionalFeeAmount) + newRewardAmount, err := subnetAInfo.TeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent.RewardRedeemer, + mockTokenAddress) Expect(err).Should(BeNil()) - Expect(amount).Should(Equal(additionalFeeAmount.Add(additionalFeeAmount, initFeeAmount))) + Expect(newRewardAmount).Should(Equal(new(big.Int).Add(initialRewardAmount, expectedIncrease))) - utils.RedeemRelayerRewardsAndConfirm( - ctx, subnetAInfo, mockToken, mockTokenAddress, fundedKey, amount, - ) + // If the funded address is the one able to redeem the rewards, do so and check the reward amount is reset. + if fundedAddress == receiveEvent.RewardRedeemer { + utils.RedeemRelayerRewardsAndConfirm( + ctx, subnetAInfo, mockToken, mockTokenAddress, fundedKey, newRewardAmount, + ) + } } diff --git a/tests/basic_send_receive.go b/tests/flows/basic_send_receive.go similarity index 75% rename from tests/basic_send_receive.go rename to tests/flows/basic_send_receive.go index 621bf1cf0..f45313303 100644 --- a/tests/basic_send_receive.go +++ b/tests/flows/basic_send_receive.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -6,23 +6,15 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" "github.com/ethereum/go-ethereum/common" . "github.com/onsi/gomega" ) // Tests basic one-way send from Subnet A to Subnet B and vice versa -func BasicSendReceive(network network.Network) { - var ( - teleporterMessageID *big.Int - ) - - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func BasicSendReceive(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) teleporterContractAddress := network.GetTeleporterContractAddress() fundedAddress, fundedKey := network.GetFundedAccountInfo() @@ -32,12 +24,12 @@ func BasicSendReceive(network network.Network) { ctx := context.Background() feeAmount := big.NewInt(1) - feeTokenAddress, feeToken := localUtils.DeployExampleERC20( + feeTokenAddress, feeToken := utils.DeployExampleERC20( ctx, fundedKey, subnetAInfo, ) - localUtils.ExampleERC20Approve( + utils.ERC20Approve( ctx, feeToken, teleporterContractAddress, @@ -70,7 +62,11 @@ func BasicSendReceive(network network.Network) { // // Relay the message to the destination // - network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, true) + deliveryReceipt := network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, true) + receiveEvent, err := utils.GetEventFromLogs( + deliveryReceipt.Logs, + subnetBInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) // // Check Teleporter message received on the destination @@ -108,7 +104,11 @@ func BasicSendReceive(network network.Network) { Expect(err).Should(BeNil()) Expect(delivered).Should(BeTrue()) - utils.RedeemRelayerRewardsAndConfirm( - ctx, subnetAInfo, feeToken, feeTokenAddress, fundedKey, feeAmount, - ) + // If the reward address of the message from A->B is the funded address, which is able to send + // transactions on subnet A, then redeem the rewards. + if receiveEvent.RewardRedeemer == fundedAddress { + utils.RedeemRelayerRewardsAndConfirm( + ctx, subnetAInfo, feeToken, feeTokenAddress, fundedKey, feeAmount, + ) + } } diff --git a/tests/block_hash_publish_receive.go b/tests/flows/block_hash_publish_receive.go similarity index 52% rename from tests/block_hash_publish_receive.go rename to tests/flows/block_hash_publish_receive.go index 7e6a1d630..4e7bf745e 100644 --- a/tests/block_hash_publish_receive.go +++ b/tests/flows/block_hash_publish_receive.go @@ -1,46 +1,41 @@ -package tests +package flows import ( "context" "math/big" "github.com/ava-labs/subnet-evm/accounts/abi/bind" - "github.com/ava-labs/teleporter/tests/network" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/utils" . "github.com/onsi/gomega" ) -func BlockHashPublishReceive(network network.Network) { - var ( - subnets = network.GetSubnetsInfo() - subnetAInfo = subnets[0] - subnetBInfo = subnets[1] - - _, fundedKey = network.GetFundedAccountInfo() - - ctx = context.Background() - - publisherAddress, publisher = localUtils.DeployBlockHashPublisher( - ctx, - fundedKey, - subnetAInfo, - ) - receiverAddress, receiver = localUtils.DeployBlockHashReceiver( - ctx, - fundedKey, - subnetBInfo, - publisherAddress, - subnetAInfo.BlockchainID, - ) +func BlockHashPublishReceive(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) + _, fundedKey := network.GetFundedAccountInfo() + + ctx := context.Background() + + publisherAddress, publisher := utils.DeployBlockHashPublisher( + ctx, + fundedKey, + subnetAInfo, + ) + receiverAddress, receiver := utils.DeployBlockHashReceiver( + ctx, + fundedKey, + subnetBInfo, + publisherAddress, + subnetAInfo.BlockchainID, ) // gather expectations - expectedBlockNumberU64, err := subnetAInfo.ChainRPCClient.BlockNumber(ctx) + expectedBlockNumberU64, err := subnetAInfo.RPCClient.BlockNumber(ctx) Expect(err).Should(BeNil()) expectedBlockNumber := big.NewInt(0).SetUint64(expectedBlockNumberU64) - block, err := subnetAInfo.ChainRPCClient.BlockByNumber( + block, err := subnetAInfo.RPCClient.BlockByNumber( ctx, expectedBlockNumber) Expect(err).Should(BeNil()) expectedBlockHash := block.Hash() @@ -48,14 +43,14 @@ func BlockHashPublishReceive(network network.Network) { // publish latest block hash tx_opts, err := bind.NewKeyedTransactorWithChainID( - fundedKey, subnetAInfo.ChainIDInt) + fundedKey, subnetAInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err := publisher.PublishLatestBlockHash( tx_opts, subnetBInfo.BlockchainID, receiverAddress) Expect(err).Should(BeNil()) - receipt, err := bind.WaitMined(ctx, subnetAInfo.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, subnetAInfo.RPCClient, tx) Expect(err).Should(BeNil()) // relay publication diff --git a/tests/deliver_to_nonexistent_contract.go b/tests/flows/deliver_to_nonexistent_contract.go similarity index 86% rename from tests/deliver_to_nonexistent_contract.go rename to tests/flows/deliver_to_nonexistent_contract.go index 725e146ab..3554075d6 100644 --- a/tests/deliver_to_nonexistent_contract.go +++ b/tests/flows/deliver_to_nonexistent_contract.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -7,9 +7,8 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" examplecrosschainmessenger "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ExampleMessenger/ExampleCrossChainMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" deploymentUtils "github.com/ava-labs/teleporter/utils/deployment-utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -17,11 +16,8 @@ import ( . "github.com/onsi/gomega" ) -func DeliverToNonExistentContract(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func DeliverToNonExistentContract(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) _, fundedKey := network.GetFundedAccountInfo() deployerKey, err := crypto.GenerateKey() @@ -45,10 +41,10 @@ func DeliverToNonExistentContract(network network.Network) { // Send a message that should fail to be executed on Subnet B // log.Info("Deploying ExampleMessenger to Subnet A") - _, subnetAExampleMessenger := localUtils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) + _, subnetAExampleMessenger := utils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) // Derive the eventual address of the destination contract on Subnet B - nonce, err := subnetBInfo.ChainRPCClient.NonceAt(ctx, deployerAddress, nil) + nonce, err := subnetBInfo.RPCClient.NonceAt(ctx, deployerAddress, nil) Expect(err).Should(BeNil()) destinationContractAddress, err := deploymentUtils.DeriveEVMContractAddress(deployerAddress, nonce) Expect(err).Should(BeNil()) @@ -59,7 +55,7 @@ func DeliverToNonExistentContract(network network.Network) { log.Info("Calling ExampleMessenger on Subnet A") message := "Hello, world!" optsA, err := bind.NewKeyedTransactorWithChainID( - fundedKey, subnetAInfo.ChainIDInt) + fundedKey, subnetAInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err := subnetAExampleMessenger.SendMessage( optsA, @@ -73,7 +69,7 @@ func DeliverToNonExistentContract(network network.Network) { Expect(err).Should(BeNil()) // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnetAInfo.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, subnetAInfo.RPCClient, tx) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -121,7 +117,7 @@ func DeliverToNonExistentContract(network network.Network) { // log.Info("Deploying the contract on Subnet B") exampleMessengerContractB, subnetBExampleMessenger := - localUtils.DeployExampleCrossChainMessenger(ctx, deployerKey, subnetBInfo) + utils.DeployExampleCrossChainMessenger(ctx, deployerKey, subnetBInfo) // Confirm that it was deployed at the expected address Expect(exampleMessengerContractB).Should(Equal(destinationContractAddress)) diff --git a/tests/flows/deliver_to_wrong_chain.go b/tests/flows/deliver_to_wrong_chain.go new file mode 100644 index 000000000..9eeb71726 --- /dev/null +++ b/tests/flows/deliver_to_wrong_chain.go @@ -0,0 +1,110 @@ +package flows + +import ( + "context" + "crypto/ecdsa" + "math/big" + + "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ava-labs/subnet-evm/core/types" + teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + . "github.com/onsi/gomega" +) + +func DeliverToWrongChain(network interfaces.Network) { + subnetAInfo, subnetBInfo, subnetCInfo := utils.GetThreeSubnets(network) + fundedAddress, fundedKey := network.GetFundedAccountInfo() + + // + // Get the expected teleporter message ID for Subnet C + // + expectedAtoCMessageID, err := + subnetAInfo.TeleporterMessenger.GetNextMessageID(&bind.CallOpts{}, subnetCInfo.BlockchainID) + Expect(err).Should(BeNil()) + + // + // Submit a message to be sent from SubnetA to SubnetB + // + ctx := context.Background() + sendCrossChainMessageInput := teleportermessenger.TeleporterMessageInput{ + DestinationBlockchainID: subnetBInfo.SubnetID, // Message intended for SubnetB + DestinationAddress: common.HexToAddress("0x1111111111111111111111111111111111111111"), + FeeInfo: teleportermessenger.TeleporterFeeInfo{ + FeeTokenAddress: fundedAddress, + Amount: big.NewInt(0), + }, + RequiredGasLimit: big.NewInt(1), + AllowedRelayerAddresses: []common.Address{}, + Message: []byte{1, 2, 3, 4}, + } + + log.Info( + "Sending Teleporter transaction on source chain", + "destinationChainID", subnetBInfo.BlockchainID, + ) + + receipt, _ := utils.SendCrossChainMessageAndWaitForAcceptance( + ctx, + subnetAInfo, + subnetBInfo, + sendCrossChainMessageInput, + fundedKey, + ) + + if network.SupportsIndependentRelaying() { + // + // Try to relay the message to subnet C, should fail + // + network.RelayMessage(ctx, receipt, subnetAInfo, subnetCInfo, false) + } else { + // + // Wait for external relayer to properly deliver the message to subnet B + // + deliveryReceipt := network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, true) + deliveryTx, isPending, err := subnetBInfo.RPCClient.TransactionByHash(ctx, deliveryReceipt.TxHash) + Expect(err).Should(BeNil()) + Expect(isPending).Should(BeFalse()) + + // + // Take the successful delivery transaction, and use it to create a transaction that attempts to deliver + // the same message to subnet C. + // + wrongChainDeliveryTx := createWrongChainDeliveryTransaction(ctx, deliveryTx, fundedKey, fundedAddress, subnetCInfo) + utils.SendTransactionAndWaitForAcceptance(ctx, subnetCInfo, wrongChainDeliveryTx, false) + } + + // + // Check that the message was not received on the Subnet C + // + delivered, err := subnetCInfo.TeleporterMessenger.MessageReceived( + &bind.CallOpts{}, subnetAInfo.BlockchainID, expectedAtoCMessageID, + ) + Expect(err).Should(BeNil()) + Expect(delivered).Should(BeFalse()) +} + +func createWrongChainDeliveryTransaction( + ctx context.Context, + deliveryTx *types.Transaction, + fundedKey *ecdsa.PrivateKey, + fundedAddress common.Address, + destination interfaces.SubnetTestInfo, +) *types.Transaction { + gasFeeCap, gasTipCap, nonce := utils.CalculateTxParams(ctx, destination, fundedAddress) + unsignedTx := types.NewTx(&types.DynamicFeeTx{ + ChainID: destination.EVMChainID, + Nonce: nonce, + To: deliveryTx.To(), + Gas: deliveryTx.Gas(), + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Value: big.NewInt(0), + Data: deliveryTx.Data(), + AccessList: deliveryTx.AccessList(), + }) + return utils.SignTransaction(unsignedTx, fundedKey, destination.EVMChainID) +} diff --git a/tests/erc20_bridge_multihop.go b/tests/flows/erc20_bridge_multihop.go similarity index 80% rename from tests/erc20_bridge_multihop.go rename to tests/flows/erc20_bridge_multihop.go index 877b81253..a959ae619 100644 --- a/tests/erc20_bridge_multihop.go +++ b/tests/flows/erc20_bridge_multihop.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -11,60 +11,51 @@ import ( bridgetoken "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ERC20Bridge/BridgeToken" erc20bridge "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ERC20Bridge/ERC20Bridge" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" . "github.com/onsi/gomega" ) -func ERC20BridgeMultihopGinkgo() { - ERC20BridgeMultihop(&network.LocalNetwork{}) -} - -func ERC20BridgeMultihop(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 3)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] - subnetCInfo := subnets[2] +func ERC20BridgeMultihop(network interfaces.Network) { + subnetAInfo, subnetBInfo, subnetCInfo := utils.GetThreeSubnets(network) teleporterContractAddress := network.GetTeleporterContractAddress() fundedAddress, fundedKey := network.GetFundedAccountInfo() ctx := context.Background() subnetATeleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( teleporterContractAddress, - subnetAInfo.ChainRPCClient, + subnetAInfo.RPCClient, ) Expect(err).Should(BeNil()) subnetBTeleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( teleporterContractAddress, - subnetBInfo.ChainRPCClient, + subnetBInfo.RPCClient, ) Expect(err).Should(BeNil()) subnetCTeleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( teleporterContractAddress, - subnetCInfo.ChainRPCClient, + subnetCInfo.RPCClient, ) Expect(err).Should(BeNil()) // Deploy an ERC20 to subnet A - nativeERC20Address, nativeERC20 := localUtils.DeployExampleERC20( + nativeERC20Address, nativeERC20 := utils.DeployExampleERC20( context.Background(), fundedKey, subnetAInfo, ) // Deploy the ERC20 bridge to subnet A - erc20BridgeAddressA, erc20BridgeA := localUtils.DeployERC20Bridge(ctx, fundedKey, subnetAInfo) + erc20BridgeAddressA, erc20BridgeA := utils.DeployERC20Bridge(ctx, fundedKey, subnetAInfo) // Deploy the ERC20 bridge to subnet B - erc20BridgeAddressB, erc20BridgeB := localUtils.DeployERC20Bridge(ctx, fundedKey, subnetBInfo) + erc20BridgeAddressB, erc20BridgeB := utils.DeployERC20Bridge(ctx, fundedKey, subnetBInfo) // Deploy the ERC20 bridge to subnet C - erc20BridgeAddressC, erc20BridgeC := localUtils.DeployERC20Bridge(ctx, fundedKey, subnetCInfo) + erc20BridgeAddressC, erc20BridgeC := utils.DeployERC20Bridge(ctx, fundedKey, subnetCInfo) amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10000000000000)) - localUtils.ExampleERC20Approve( + utils.ERC20Approve( ctx, nativeERC20, erc20BridgeAddressA, @@ -74,7 +65,6 @@ func ERC20BridgeMultihop(network network.Network) { ) // Send a transaction on Subnet A to add support for the the ERC20 token to the bridge on Subnet B - createBridgeTokenMessageFeeAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1)) receipt, messageID := submitCreateBridgeToken( ctx, subnetAInfo, @@ -82,7 +72,7 @@ func ERC20BridgeMultihop(network network.Network) { erc20BridgeAddressB, nativeERC20Address, nativeERC20Address, - createBridgeTokenMessageFeeAmount, + big.NewInt(0), fundedAddress, fundedKey, erc20BridgeA, @@ -91,6 +81,7 @@ func ERC20BridgeMultihop(network network.Network) { // Relay message network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, true) + // Check Teleporter message received on the destination delivered, err := subnetBTeleporterMessenger.MessageReceived( &bind.CallOpts{}, @@ -109,10 +100,35 @@ func ERC20BridgeMultihop(network network.Network) { ) Expect(err).Should(BeNil()) Expect(bridgeTokenSubnetBAddress).ShouldNot(Equal(common.Address{})) - bridgeTokenB, err := bridgetoken.NewBridgeToken(bridgeTokenSubnetBAddress, subnetBInfo.ChainRPCClient) + bridgeTokenB, err := bridgetoken.NewBridgeToken(bridgeTokenSubnetBAddress, subnetBInfo.RPCClient) + Expect(err).Should(BeNil()) + + // Check all the settings of the new bridge token are correct. + actualNativeChainID, err := bridgeTokenB.NativeBlockchainID(&bind.CallOpts{}) + Expect(err).Should(BeNil()) + Expect(actualNativeChainID[:]).Should(Equal(subnetAInfo.BlockchainID[:])) + + actualNativeBridgeAddress, err := bridgeTokenB.NativeBridge(&bind.CallOpts{}) Expect(err).Should(BeNil()) + Expect(actualNativeBridgeAddress).Should(Equal(erc20BridgeAddressA)) + + actualNativeAssetAddress, err := bridgeTokenB.NativeAsset(&bind.CallOpts{}) + Expect(err).Should(BeNil()) + Expect(actualNativeAssetAddress).Should(Equal(nativeERC20Address)) - // Send a transaction on Subnet B to add support for the the ERC20 token to the bridge on Subnet C + actualName, err := bridgeTokenB.Name(&bind.CallOpts{}) + Expect(err).Should(BeNil()) + Expect(actualName).Should(Equal("Mock Token")) + + actualSymbol, err := bridgeTokenB.Symbol(&bind.CallOpts{}) + Expect(err).Should(BeNil()) + Expect(actualSymbol).Should(Equal("EXMP")) + + actualDecimals, err := bridgeTokenB.Decimals(&bind.CallOpts{}) + Expect(err).Should(BeNil()) + Expect(actualDecimals).Should(Equal(uint8(18))) + + // Send a transaction on Subnet A to add support for the the ERC20 token to the bridge on Subnet C receipt, messageID = submitCreateBridgeToken( ctx, subnetAInfo, @@ -120,14 +136,16 @@ func ERC20BridgeMultihop(network network.Network) { erc20BridgeAddressC, nativeERC20Address, nativeERC20Address, - createBridgeTokenMessageFeeAmount, + big.NewInt(0), fundedAddress, fundedKey, erc20BridgeA, subnetATeleporterMessenger, ) + // Relay message network.RelayMessage(ctx, receipt, subnetAInfo, subnetCInfo, true) + // Check Teleporter message received on the destination delivered, err = subnetCTeleporterMessenger.MessageReceived( &bind.CallOpts{}, @@ -146,13 +164,12 @@ func ERC20BridgeMultihop(network network.Network) { ) Expect(err).Should(BeNil()) Expect(bridgeTokenSubnetCAddress).ShouldNot(Equal(common.Address{})) - bridgeTokenC, err := bridgetoken.NewBridgeToken(bridgeTokenSubnetCAddress, subnetCInfo.ChainRPCClient) + bridgeTokenC, err := bridgetoken.NewBridgeToken(bridgeTokenSubnetCAddress, subnetCInfo.RPCClient) Expect(err).Should(BeNil()) // Send a bridge transfer for the newly added token from subnet A to subnet B totalAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(13)) primaryFeeAmount := big.NewInt(1e18) - secondaryFeeAmount := big.NewInt(0) receipt, messageID = bridgeToken( ctx, subnetAInfo, @@ -162,7 +179,7 @@ func ERC20BridgeMultihop(network network.Network) { fundedAddress, totalAmount, primaryFeeAmount, - secondaryFeeAmount, + big.NewInt(0), fundedAddress, fundedKey, erc20BridgeA, @@ -172,43 +189,23 @@ func ERC20BridgeMultihop(network network.Network) { ) // Relay message - network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, true) + deliveryReceipt := network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, true) + receiveEvent, err := utils.GetEventFromLogs( + deliveryReceipt.Logs, + subnetBInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) + // Check Teleporter message received on the destination delivered, err = subnetBTeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetAInfo.BlockchainID, messageID) Expect(err).Should(BeNil()) Expect(delivered).Should(BeTrue()) - // Check all the settings of the new bridge token are correct. - actualNativeChainID, err := bridgeTokenB.NativeBlockchainID(&bind.CallOpts{}) - Expect(err).Should(BeNil()) - Expect(actualNativeChainID[:]).Should(Equal(subnetAInfo.BlockchainID[:])) - - actualNativeBridgeAddress, err := bridgeTokenB.NativeBridge(&bind.CallOpts{}) - Expect(err).Should(BeNil()) - Expect(actualNativeBridgeAddress).Should(Equal(erc20BridgeAddressA)) - - actualNativeAssetAddress, err := bridgeTokenB.NativeAsset(&bind.CallOpts{}) - Expect(err).Should(BeNil()) - Expect(actualNativeAssetAddress).Should(Equal(nativeERC20Address)) - - actualName, err := bridgeTokenB.Name(&bind.CallOpts{}) - Expect(err).Should(BeNil()) - Expect(actualName).Should(Equal("Mock Token")) - - actualSymbol, err := bridgeTokenB.Symbol(&bind.CallOpts{}) - Expect(err).Should(BeNil()) - Expect(actualSymbol).Should(Equal("EXMP")) - - actualDecimals, err := bridgeTokenB.Decimals(&bind.CallOpts{}) - Expect(err).Should(BeNil()) - Expect(actualDecimals).Should(Equal(uint8(18))) - // Check the recipient balance of the new bridge token. actualRecipientBalance, err := bridgeTokenB.BalanceOf(&bind.CallOpts{}, fundedAddress) Expect(err).Should(BeNil()) Expect(actualRecipientBalance).Should(Equal(totalAmount.Sub(totalAmount, primaryFeeAmount))) - // Approve the bridge contract on subnet B to spent the wrapped tokens in the user account. + // Approve the bridge contract on subnet B to spend the wrapped tokens in the user account. approveBridgeToken( ctx, subnetBInfo, @@ -220,10 +217,16 @@ func ERC20BridgeMultihop(network network.Network) { fundedKey, ) + // Check the initial relayer reward amount on SubnetA. + currentRewardAmount, err := subnetAInfo.TeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent.RewardRedeemer, + nativeERC20Address) + Expect(err).Should(BeNil()) + // Unwrap bridged tokens back to subnet A, then wrap tokens to final destination on subnet C totalAmount = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(11)) - primaryFeeAmount = big.NewInt(1e18) - secondaryFeeAmount = big.NewInt(1e18) + secondaryFeeAmount := big.NewInt(1e18) receipt, messageID = bridgeToken( ctx, subnetBInfo, @@ -243,7 +246,10 @@ func ERC20BridgeMultihop(network network.Network) { ) // Relay message from SubnetB to SubnetA + // The receipt of transaction that delivers the message will also have the "second hop" + // message sent from subnet A to subnet C. receipt = network.RelayMessage(ctx, receipt, subnetBInfo, subnetAInfo, true) + // Check Teleporter message received on the destination delivered, err = subnetATeleporterMessenger.MessageReceived( &bind.CallOpts{}, @@ -253,14 +259,28 @@ func ERC20BridgeMultihop(network network.Network) { Expect(err).Should(BeNil()) Expect(delivered).Should(BeTrue()) - // Get the sendCrossChainMessage event from SubnetA to SubnetC + // Get the sendCrossChainMessage event from SubnetA to SubnetC, which should be present + // the receipt of the transaction that delivered the first message from SubnetB to SubnetA. event, err := utils.GetEventFromLogs(receipt.Logs, subnetATeleporterMessenger.ParseSendCrossChainMessage) Expect(err).Should(BeNil()) Expect(event.DestinationBlockchainID[:]).Should(Equal(subnetCInfo.BlockchainID[:])) messageID = event.Message.MessageID + // Check the redeemable reward balance of the relayer if the relayer address was set + updatedRewardAmount, err := subnetATeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent.RewardRedeemer, + nativeERC20Address, + ) + Expect(err).Should(BeNil()) + Expect(updatedRewardAmount).Should(Equal(new(big.Int).Add(currentRewardAmount, primaryFeeAmount))) + // Relay message from SubnetA to SubnetC - network.RelayMessage(ctx, receipt, subnetAInfo, subnetCInfo, true) + deliveryReceipt = network.RelayMessage(ctx, receipt, subnetAInfo, subnetCInfo, true) + receiveEvent, err = utils.GetEventFromLogs( + deliveryReceipt.Logs, + subnetCInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) // Check Teleporter message received on the destination delivered, err = subnetCTeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetAInfo.BlockchainID, messageID) @@ -272,15 +292,6 @@ func ERC20BridgeMultihop(network network.Network) { expectedAmount := totalAmount.Sub(totalAmount, primaryFeeAmount).Sub(totalAmount, secondaryFeeAmount) Expect(actualRecipientBalance).Should(Equal(expectedAmount)) - // Check the redeemable reward balance of the relayer if the relayer address was set - actualRelayerRedeemableBalance, err := subnetATeleporterMessenger.CheckRelayerRewardAmount( - &bind.CallOpts{}, - fundedAddress, - nativeERC20Address, - ) - Expect(err).Should(BeNil()) - Expect(actualRelayerRedeemableBalance).Should(Equal(primaryFeeAmount.Add(primaryFeeAmount, secondaryFeeAmount))) - // Approve the bridge contract on Subnet C to spend the bridge tokens from the user account approveBridgeToken( ctx, @@ -292,10 +303,15 @@ func ERC20BridgeMultihop(network network.Network) { fundedAddress, fundedKey) + // Get the current relayer reward amount on SubnetA. + currentRewardAmount, err = subnetAInfo.TeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent.RewardRedeemer, + nativeERC20Address) + Expect(err).Should(BeNil()) + // Send a transaction to unwrap tokens from Subnet C back to Subnet A totalAmount = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(8)) - primaryFeeAmount = big.NewInt(1e18) - secondaryFeeAmount = big.NewInt(0) receipt, messageID = bridgeToken( ctx, subnetCInfo, @@ -305,7 +321,7 @@ func ERC20BridgeMultihop(network network.Network) { fundedAddress, totalAmount, primaryFeeAmount, - secondaryFeeAmount, + big.NewInt(0), fundedAddress, fundedKey, erc20BridgeC, @@ -316,6 +332,7 @@ func ERC20BridgeMultihop(network network.Network) { // Relay message from SubnetC to SubnetA network.RelayMessage(ctx, receipt, subnetCInfo, subnetAInfo, true) + // Check Teleporter message received on the destination delivered, err = subnetATeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetCInfo.BlockchainID, messageID) Expect(err).Should(BeNil()) @@ -324,23 +341,22 @@ func ERC20BridgeMultihop(network network.Network) { // Check the balance of the native token after the unwrap actualNativeTokenDefaultAccountBalance, err := nativeERC20.BalanceOf(&bind.CallOpts{}, fundedAddress) Expect(err).Should(BeNil()) - expectedAmount = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(9999999992)) + expectedAmount = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(9999999994)) Expect(actualNativeTokenDefaultAccountBalance).Should(Equal(expectedAmount)) // Check the balance of the native token for the relayer, which should have received the fee rewards - actualRelayerRedeemableBalance, err = subnetATeleporterMessenger.CheckRelayerRewardAmount( + updatedRewardAmount, err = subnetATeleporterMessenger.CheckRelayerRewardAmount( &bind.CallOpts{}, - fundedAddress, + receiveEvent.RewardRedeemer, nativeERC20Address, ) Expect(err).Should(BeNil()) - expectedAmount = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(4)) - Expect(actualRelayerRedeemableBalance).Should(Equal(expectedAmount)) + Expect(updatedRewardAmount).Should(Equal(new(big.Int).Add(currentRewardAmount, secondaryFeeAmount))) } func submitCreateBridgeToken( ctx context.Context, - source utils.SubnetTestInfo, + source interfaces.SubnetTestInfo, destinationChainID ids.ID, destinationBridgeAddress common.Address, nativeToken common.Address, @@ -351,7 +367,7 @@ func submitCreateBridgeToken( transactor *erc20bridge.ERC20Bridge, teleporterMessenger *teleportermessenger.TeleporterMessenger, ) (*types.Receipt, *big.Int) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) + opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.EVMChainID) Expect(err).Should(BeNil()) tx, err := transactor.SubmitCreateBridgeToken( @@ -365,7 +381,7 @@ func submitCreateBridgeToken( Expect(err).Should(BeNil()) // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, source.RPCClient, tx) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -382,10 +398,10 @@ func submitCreateBridgeToken( func bridgeToken( ctx context.Context, - source utils.SubnetTestInfo, + source interfaces.SubnetTestInfo, destinationChainID ids.ID, destinationBridgeAddress common.Address, - nativeToken common.Address, + token common.Address, recipient common.Address, totalAmount *big.Int, primaryFeeAmount *big.Int, @@ -397,14 +413,14 @@ func bridgeToken( nativeTokenChainID ids.ID, teleporterMessenger *teleportermessenger.TeleporterMessenger, ) (*types.Receipt, *big.Int) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) + opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.EVMChainID) Expect(err).Should(BeNil()) tx, err := transactor.BridgeTokens( opts, destinationChainID, destinationBridgeAddress, - nativeToken, + token, recipient, totalAmount, primaryFeeAmount, @@ -413,7 +429,7 @@ func bridgeToken( Expect(err).Should(BeNil()) // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, source.RPCClient, tx) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -430,7 +446,7 @@ func bridgeToken( func approveBridgeToken( ctx context.Context, - source utils.SubnetTestInfo, + source interfaces.SubnetTestInfo, bridgeTokenAddress common.Address, transactor *bridgetoken.BridgeToken, amount *big.Int, @@ -438,13 +454,13 @@ func approveBridgeToken( fundedAddress common.Address, fundedKey *ecdsa.PrivateKey, ) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) + opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.EVMChainID) Expect(err).Should(BeNil()) txn, err := transactor.Approve(opts, spender, amount) Expect(err).Should(BeNil()) - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, txn) + receipt, err := bind.WaitMined(ctx, source.RPCClient, txn) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) } diff --git a/tests/example_messenger.go b/tests/flows/example_messenger.go similarity index 74% rename from tests/example_messenger.go rename to tests/flows/example_messenger.go index 8fccd0d31..5a0a71be1 100644 --- a/tests/example_messenger.go +++ b/tests/flows/example_messenger.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -7,22 +7,14 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" examplecrosschainmessenger "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ExampleMessenger/ExampleCrossChainMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" "github.com/ethereum/go-ethereum/common" . "github.com/onsi/gomega" ) -func ExampleMessengerGinkgo() { - ExampleMessenger(&network.LocalNetwork{}) -} - -func ExampleMessenger(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func ExampleMessenger(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) _, fundedKey := network.GetFundedAccountInfo() // @@ -30,8 +22,8 @@ func ExampleMessenger(network network.Network) { // ctx := context.Background() - _, subnetAExampleMessenger := localUtils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) - exampleMessengerContractB, subnetBExampleMessenger := localUtils.DeployExampleCrossChainMessenger( + _, subnetAExampleMessenger := utils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) + exampleMessengerContractB, subnetBExampleMessenger := utils.DeployExampleCrossChainMessenger( ctx, fundedKey, subnetBInfo, ) @@ -39,7 +31,7 @@ func ExampleMessenger(network network.Network) { // Call the example messenger contract on Subnet A // message := "Hello, world!" - optsA, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.ChainIDInt) + optsA, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err := subnetAExampleMessenger.SendMessage( optsA, @@ -53,7 +45,7 @@ func ExampleMessenger(network network.Network) { Expect(err).Should(BeNil()) // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnetAInfo.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, subnetAInfo.RPCClient, tx) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) diff --git a/tests/insufficient_gas.go b/tests/flows/insufficient_gas.go similarity index 82% rename from tests/insufficient_gas.go rename to tests/flows/insufficient_gas.go index aa0926f19..28555ecda 100644 --- a/tests/insufficient_gas.go +++ b/tests/flows/insufficient_gas.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -6,31 +6,27 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" . "github.com/onsi/gomega" ) -func InsufficientGas(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func InsufficientGas(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) fundedAddress, fundedKey := network.GetFundedAccountInfo() ctx := context.Background() // Deploy ExampleMessenger to Subnets A - _, subnetAExampleMessenger := localUtils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) + _, subnetAExampleMessenger := utils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) // Deploy ExampleMessenger to Subnets B exampleMessengerContractB, subnetBExampleMessenger := - localUtils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetBInfo) + utils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetBInfo) // Send message from SubnetA to SubnetB with 0 execution gas, which should fail to execute message := "Hello, world!" optsA, err := bind.NewKeyedTransactorWithChainID( - fundedKey, subnetAInfo.ChainIDInt) + fundedKey, subnetAInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err := subnetAExampleMessenger.SendMessage( optsA, subnetBInfo.BlockchainID, exampleMessengerContractB, fundedAddress, big.NewInt(0), big.NewInt(0), message, @@ -38,7 +34,7 @@ func InsufficientGas(network network.Network) { Expect(err).Should(BeNil()) // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnetAInfo.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, subnetAInfo.RPCClient, tx) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) diff --git a/tests/receive_message_twice.go b/tests/flows/relay_message_twice.go similarity index 86% rename from tests/receive_message_twice.go rename to tests/flows/relay_message_twice.go index 6c5654eeb..73d306cca 100644 --- a/tests/receive_message_twice.go +++ b/tests/flows/relay_message_twice.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -6,7 +6,7 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -14,11 +14,8 @@ import ( . "github.com/onsi/gomega" ) -func ReceiveMessageTwice(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func RelayMessageTwice(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) fundedAddress, fundedKey := network.GetFundedAccountInfo() // @@ -62,7 +59,7 @@ func ReceiveMessageTwice(network network.Network) { Expect(delivered).Should(BeTrue()) // - // Attempt to send the same message again + // Attempt to send the same message again, should fail // log.Info("Relaying the same Teleporter message again on the destination") network.RelayMessage(ctx, receipt, subnetAInfo, subnetBInfo, false) diff --git a/tests/relayer_modifies_message.go b/tests/flows/relayer_modifies_message.go similarity index 85% rename from tests/relayer_modifies_message.go rename to tests/flows/relayer_modifies_message.go index 462bfe4ed..0d449a651 100644 --- a/tests/relayer_modifies_message.go +++ b/tests/flows/relayer_modifies_message.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -12,9 +12,8 @@ import ( predicateutils "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/x/warp" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" gasUtils "github.com/ava-labs/teleporter/utils/gas-utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -23,12 +22,8 @@ import ( ) // Disallow this test from being run on anything but a local network, since it requires special behavior by the relayer -func RelayerModifiesMessage() { - network := &network.LocalNetwork{} - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func RelayerModifiesMessage(network interfaces.LocalNetwork) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) fundedAddress, fundedKey := network.GetFundedAccountInfo() // Send a transaction to Subnet A to issue a Warp Message from the Teleporter contract to Subnet B @@ -51,7 +46,12 @@ func RelayerModifiesMessage() { // Relay the message to the destination // Relayer modifies the message in flight - relayAlteredMessage(ctx, receipt, subnetAInfo, subnetBInfo, fundedKey, network.GetTeleporterContractAddress()) + relayAlteredMessage( + ctx, + receipt, + subnetAInfo, + subnetBInfo, + network) // Check Teleporter message was not received on the destination delivered, err := @@ -63,24 +63,24 @@ func RelayerModifiesMessage() { func relayAlteredMessage( ctx context.Context, sourceReceipt *types.Receipt, - source utils.SubnetTestInfo, - destination utils.SubnetTestInfo, - fundedKey *ecdsa.PrivateKey, - teleporterContractAddress common.Address, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, + network interfaces.LocalNetwork, ) { // Fetch the Teleporter message from the logs sendEvent, err := utils.GetEventFromLogs(sourceReceipt.Logs, source.TeleporterMessenger.ParseSendCrossChainMessage) Expect(err).Should(BeNil()) - signedWarpMessageBytes := localUtils.ConstructSignedWarpMessageBytes(ctx, sourceReceipt, source, destination) + signedWarpMessageBytes := network.ConstructSignedWarpMessageBytes(ctx, sourceReceipt, source, destination) // Construct the transaction to send the Warp message to the destination chain + _, fundedKey := network.GetFundedAccountInfo() signedTx := createAlteredReceiveCrossChainMessageTransaction( ctx, signedWarpMessageBytes, sendEvent.Message.RequiredGasLimit, - teleporterContractAddress, + network.GetTeleporterContractAddress(), fundedKey, destination, ) @@ -95,7 +95,7 @@ func createAlteredReceiveCrossChainMessageTransaction( requiredGasLimit *big.Int, teleporterContractAddress common.Address, fundedKey *ecdsa.PrivateKey, - subnetInfo utils.SubnetTestInfo, + subnetInfo interfaces.SubnetTestInfo, ) *types.Transaction { fundedAddress := crypto.PubkeyToAddress(fundedKey.PublicKey) // Construct the transaction to send the Warp message to the destination chain @@ -117,7 +117,7 @@ func createAlteredReceiveCrossChainMessageTransaction( alterTeleporterMessage(signedMessage) destinationTx := predicateutils.NewPredicateTx( - subnetInfo.ChainIDInt, + subnetInfo.EVMChainID, nonce, &teleporterContractAddress, gasLimit, @@ -130,7 +130,7 @@ func createAlteredReceiveCrossChainMessageTransaction( signedMessage.Bytes(), ) - return utils.SignTransaction(destinationTx, fundedKey, subnetInfo.ChainIDInt) + return utils.SignTransaction(destinationTx, fundedKey, subnetInfo.EVMChainID) } func alterTeleporterMessage(signedMessage *avalancheWarp.Message) { diff --git a/tests/resubmit_altered_message.go b/tests/flows/resubmit_altered_message.go similarity index 90% rename from tests/resubmit_altered_message.go rename to tests/flows/resubmit_altered_message.go index 8b227d65a..4fae7b918 100644 --- a/tests/resubmit_altered_message.go +++ b/tests/flows/resubmit_altered_message.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -6,18 +6,15 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" . "github.com/onsi/gomega" ) -func ResubmitAlteredMessage(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func ResubmitAlteredMessage(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) fundedAddress, fundedKey := network.GetFundedAccountInfo() // Send a transaction to Subnet A to issue a Warp Message from the Teleporter contract to Subnet B @@ -62,7 +59,7 @@ func ResubmitAlteredMessage(network network.Network) { // Resubmit the altered message log.Info("Submitting the altered Teleporter message on the source chain") - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.ChainIDInt) + opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err := subnetAInfo.TeleporterMessenger.RetrySendCrossChainMessage(opts, subnetBInfo.BlockchainID, teleporterMessage) diff --git a/tests/retry_successful_execution.go b/tests/flows/retry_successful_execution.go similarity index 79% rename from tests/retry_successful_execution.go rename to tests/flows/retry_successful_execution.go index cea7350d5..fac255dfb 100644 --- a/tests/retry_successful_execution.go +++ b/tests/flows/retry_successful_execution.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -7,17 +7,13 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" examplecrosschainmessenger "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ExampleMessenger/ExampleCrossChainMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" . "github.com/onsi/gomega" ) -func RetrySuccessfulExecution(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func RetrySuccessfulExecution(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) fundedAddress, fundedKey := network.GetFundedAccountInfo() // @@ -25,8 +21,8 @@ func RetrySuccessfulExecution(network network.Network) { // ctx := context.Background() - _, subnetAExampleMessenger := localUtils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) - exampleMessengerContractAddressB, subnetBExampleMessenger := localUtils.DeployExampleCrossChainMessenger( + _, subnetAExampleMessenger := utils.DeployExampleCrossChainMessenger(ctx, fundedKey, subnetAInfo) + exampleMessengerContractAddressB, subnetBExampleMessenger := utils.DeployExampleCrossChainMessenger( ctx, fundedKey, subnetBInfo, ) @@ -34,7 +30,7 @@ func RetrySuccessfulExecution(network network.Network) { // Call the example messenger contract on Subnet A // message := "Hello, world!" - optsA, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.ChainIDInt) + optsA, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err := subnetAExampleMessenger.SendMessage( optsA, @@ -48,7 +44,7 @@ func RetrySuccessfulExecution(network network.Network) { Expect(err).Should(BeNil()) // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnetAInfo.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, subnetAInfo.RPCClient, tx) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -86,7 +82,7 @@ func RetrySuccessfulExecution(network network.Network) { // // Attempt to retry message execution, which should fail // - optsB, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetBInfo.ChainIDInt) + optsB, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetBInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err = subnetBInfo.TeleporterMessenger.RetryMessageExecution(optsB, subnetAInfo.BlockchainID, deliveredTeleporterMessage) diff --git a/tests/flows/send_specific_receipts.go b/tests/flows/send_specific_receipts.go new file mode 100644 index 000000000..02ff5b39c --- /dev/null +++ b/tests/flows/send_specific_receipts.go @@ -0,0 +1,257 @@ +package flows + +import ( + "context" + "crypto/ecdsa" + "math/big" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/accounts/abi/bind" + teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + . "github.com/onsi/gomega" +) + +func SendSpecificReceipts(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) + teleporterContractAddress := network.GetTeleporterContractAddress() + _, fundedKey := network.GetFundedAccountInfo() + ctx := context.Background() + + // Clear the receipt queue from Subnet B -> Subnet A to have a clean slate for the test flow. + // This is only done if the test non-external networks because external networks may have + // an arbitrarily high number of receipts to be cleared from a given queue from unrelated messages. + if !network.IsExternalNetwork() { + clearReceiptQueue(ctx, network, fundedKey, subnetBInfo, subnetAInfo) + } + + // Use mock token as the fee token + mockTokenAddress, mockToken := utils.DeployExampleERC20( + ctx, fundedKey, subnetAInfo, + ) + utils.ERC20Approve( + ctx, + mockToken, + teleporterContractAddress, + big.NewInt(0).Mul(big.NewInt(1e18), + big.NewInt(10)), + subnetAInfo, + fundedKey, + ) + + // Send two messages from Subnet A to Subnet B + relayerFeePerMessage := big.NewInt(5) + destinationAddress := common.HexToAddress("0x1111111111111111111111111111111111111111") + sendCrossChainMessageInput := teleportermessenger.TeleporterMessageInput{ + DestinationBlockchainID: subnetBInfo.BlockchainID, + DestinationAddress: destinationAddress, + FeeInfo: teleportermessenger.TeleporterFeeInfo{ + FeeTokenAddress: mockTokenAddress, + Amount: relayerFeePerMessage, + }, + RequiredGasLimit: big.NewInt(1), + AllowedRelayerAddresses: []common.Address{}, + Message: []byte{1, 2, 3, 4}, + } + + // Send first message from Subnet A to Subnet B with fee amount 5 + sendCrossChainMsgReceipt, messageID1 := utils.SendCrossChainMessageAndWaitForAcceptance( + ctx, subnetAInfo, subnetBInfo, sendCrossChainMessageInput, fundedKey) + + // Relay the message from SubnetA to SubnetB + deliveryReceipt1 := network.RelayMessage(ctx, sendCrossChainMsgReceipt, subnetAInfo, subnetBInfo, true) + receiveEvent1, err := utils.GetEventFromLogs( + deliveryReceipt1.Logs, + subnetBInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) + + // Check that the first message was delivered + delivered, err := + subnetBInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetAInfo.BlockchainID, messageID1) + Expect(err).Should(BeNil()) + Expect(delivered).Should(BeTrue()) + + // Send second message from Subnet A to Subnet B with fee amount 5 + sendCrossChainMsgReceipt, messageID2 := utils.SendCrossChainMessageAndWaitForAcceptance( + ctx, subnetAInfo, subnetBInfo, sendCrossChainMessageInput, fundedKey) + + // Relay the message from SubnetA to SubnetB + deliveryReceipt2 := network.RelayMessage(ctx, sendCrossChainMsgReceipt, subnetAInfo, subnetBInfo, true) + receiveEvent2, err := utils.GetEventFromLogs( + deliveryReceipt2.Logs, + subnetBInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) + + // Check that the second message was delivered + delivered, err = + subnetBInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetAInfo.BlockchainID, messageID2) + Expect(err).Should(BeNil()) + Expect(delivered).Should(BeTrue()) + + // Call send specific receipts to get reward of relaying two messages + receipt, messageID := utils.SendSpecifiedReceiptsAndWaitForAcceptance( + ctx, + subnetAInfo.BlockchainID, + subnetBInfo, + []*big.Int{messageID1, messageID2}, + teleportermessenger.TeleporterFeeInfo{ + FeeTokenAddress: mockTokenAddress, + Amount: big.NewInt(0), + }, + []common.Address{}, + fundedKey, + ) + + // Relay message from Subnet B to Subnet A + network.RelayMessage(ctx, receipt, subnetBInfo, subnetAInfo, true) + + // Check that the message back to Subnet A was delivered + delivered, err = subnetAInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetBInfo.BlockchainID, messageID) + Expect(err).Should(BeNil()) + Expect(delivered).Should(BeTrue()) + + // Check the reward amounts. + // Even on external networks, the relayer should only have the expected fee amount + // for this asset because the asset contract was newly deployed by this test. + checkExpectedRewardAmounts(subnetAInfo, receiveEvent1, receiveEvent2, mockTokenAddress, relayerFeePerMessage) + + // If the network is internal to the test application, send a message from Subnet B to Subnet A to trigger + // the "regular" method of delivering receipts. The next message from B->A will contain the same receipts + // that were manually sent in the above steps, but they should not be processed again on Subnet A. + // These checks are not performed for external networks because unrelated messages may have already changed + // the state of the receipt queues. + if !network.IsExternalNetwork() { + sendCrossChainMessageInput = teleportermessenger.TeleporterMessageInput{ + DestinationBlockchainID: subnetAInfo.BlockchainID, + DestinationAddress: destinationAddress, + FeeInfo: teleportermessenger.TeleporterFeeInfo{ + FeeTokenAddress: mockTokenAddress, + Amount: big.NewInt(0), + }, + RequiredGasLimit: big.NewInt(1), + AllowedRelayerAddresses: []common.Address{}, + Message: []byte{1, 2, 3, 4}, + } + + // This message will also have the same receipts as the previous message + receipt, messageID = utils.SendCrossChainMessageAndWaitForAcceptance( + ctx, subnetBInfo, subnetAInfo, sendCrossChainMessageInput, fundedKey) + + // Relay message from Subnet B to Subnet A + receipt = network.RelayMessage(ctx, receipt, subnetBInfo, subnetAInfo, true) + // Check delivered + delivered, err = subnetAInfo.TeleporterMessenger.MessageReceived( + &bind.CallOpts{}, + subnetBInfo.BlockchainID, + messageID) + Expect(err).Should(BeNil()) + Expect(delivered).Should(BeTrue()) + // Get the Teleporter message from receive event and confirm that the receipts are delivered again + receiveEvent, err := + utils.GetEventFromLogs(receipt.Logs, subnetAInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) + log.Info("Receipt included", "count", len(receiveEvent.Message.Receipts), "receipts", receiveEvent.Message.Receipts) + Expect(receiptIncluded(messageID1, receiveEvent.Message.Receipts)).Should(BeTrue()) + Expect(receiptIncluded(messageID2, receiveEvent.Message.Receipts)).Should(BeTrue()) + + // Check the reward amount remains the same + checkExpectedRewardAmounts(subnetAInfo, receiveEvent1, receiveEvent2, mockTokenAddress, relayerFeePerMessage) + } +} + +func clearReceiptQueue( + ctx context.Context, + network interfaces.Network, + fundedKey *ecdsa.PrivateKey, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, +) { + outstandReceiptCount := getOutstandingReceiptCount(source, destination.BlockchainID) + for outstandReceiptCount.Cmp(big.NewInt(0)) != 0 { + log.Info("Emptying receipt queue", "remainingReceipts", outstandReceiptCount.String()) + // Send message from Subnet B to Subnet A to trigger the "regular" method of delivering receipts. + // The next message from B->A will contain the same receipts that were manually sent in the above steps, + // but they should not be processed again on Subnet A. + sendCrossChainMessageInput := teleportermessenger.TeleporterMessageInput{ + DestinationBlockchainID: destination.BlockchainID, + DestinationAddress: common.HexToAddress("0x1111111111111111111111111111111111111111"), + RequiredGasLimit: big.NewInt(1), + FeeInfo: teleportermessenger.TeleporterFeeInfo{ + FeeTokenAddress: common.Address{}, + Amount: big.NewInt(0), + }, + AllowedRelayerAddresses: []common.Address{}, + Message: []byte{1, 2, 3, 4}, + } + + // This message will also have the same receipts as the previous message + receipt, _ := utils.SendCrossChainMessageAndWaitForAcceptance( + ctx, source, destination, sendCrossChainMessageInput, fundedKey) + + // Relay message + network.RelayMessage(ctx, receipt, source, destination, true) + + outstandReceiptCount = getOutstandingReceiptCount(source, destination.BlockchainID) + } + log.Info("Receipt queue emptied") +} + +func getOutstandingReceiptCount(source interfaces.SubnetTestInfo, destinationBlockchainID ids.ID) *big.Int { + size, err := source.TeleporterMessenger.GetReceiptQueueSize(&bind.CallOpts{}, destinationBlockchainID) + Expect(err).Should(BeNil()) + return size +} + +// Checks the given message ID is included in the list of receipts. +func receiptIncluded( + expectedMessageID *big.Int, + receipts []teleportermessenger.TeleporterMessageReceipt, +) bool { + for _, receipt := range receipts { + if receipt.ReceivedMessageID.Cmp(expectedMessageID) == 0 { + return true + } + } + return false +} + +// Checks that the reward redeemers specified by the two provided message receipts +// are able to redeem the correct amount of the rewards of the given token. It is +// assumed that the {tokenAddress} was used as the fee asset for each of the messages, +// and that each message individually had a fee of {feePerMessage}. +func checkExpectedRewardAmounts( + sourceSubnet interfaces.SubnetTestInfo, + receiveEvent1 *teleportermessenger.TeleporterMessengerReceiveCrossChainMessage, + receiveEvent2 *teleportermessenger.TeleporterMessengerReceiveCrossChainMessage, + tokenAddress common.Address, + feePerMessage *big.Int, +) { + // Check the reward amounts. + // If the same address is the reward redeemer for both messages, + // it should be able to redeem {feePerMessage}*2. Otherwise, + // each distinct reward redeemer should be able to redeem {feePerMessage}. + if receiveEvent1.RewardRedeemer == receiveEvent2.RewardRedeemer { + amount, err := sourceSubnet.TeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent1.RewardRedeemer, + tokenAddress) + Expect(err).Should(BeNil()) + Expect(amount).Should(Equal(new(big.Int).Mul(feePerMessage, big.NewInt(2)))) + } else { + amount1, err := sourceSubnet.TeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent1.RewardRedeemer, + tokenAddress) + Expect(err).Should(BeNil()) + Expect(amount1).Should(Equal(feePerMessage)) + amount2, err := sourceSubnet.TeleporterMessenger.CheckRelayerRewardAmount( + &bind.CallOpts{}, + receiveEvent2.RewardRedeemer, + tokenAddress) + Expect(err).Should(BeNil()) + Expect(amount2).Should(Equal(feePerMessage)) + } +} diff --git a/tests/unallowed_relayer.go b/tests/flows/unallowed_relayer.go similarity index 87% rename from tests/unallowed_relayer.go rename to tests/flows/unallowed_relayer.go index 0f21bde3c..131b9e1ad 100644 --- a/tests/unallowed_relayer.go +++ b/tests/flows/unallowed_relayer.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -6,18 +6,15 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" . "github.com/onsi/gomega" ) -func UnallowedRelayer(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func UnallowedRelayer(network interfaces.Network) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) fundedAddress, fundedKey := network.GetFundedAccountInfo() // diff --git a/tests/validator_churn.go b/tests/flows/validator_churn.go similarity index 77% rename from tests/validator_churn.go rename to tests/flows/validator_churn.go index 926b9b580..e874c478e 100644 --- a/tests/validator_churn.go +++ b/tests/flows/validator_churn.go @@ -1,4 +1,4 @@ -package tests +package flows import ( "context" @@ -9,22 +9,20 @@ import ( "github.com/ava-labs/subnet-evm/core/types" subnetEvmUtils "github.com/ava-labs/subnet-evm/tests/utils" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" + "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" . "github.com/onsi/gomega" ) -// Disallow this test from being run on anything but a local network, since it manipulates the validator set -func ValidatorChurn() { - network := &network.LocalNetwork{} +const ( + nodesPerSubnet = 5 + newNodeCount = 5 +) - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] +func ValidatorChurn(network interfaces.LocalNetwork) { + subnetAInfo, subnetBInfo, _ := utils.GetThreeSubnets(network) teleporterContractAddress := network.GetTeleporterContractAddress() fundedAddress, fundedKey := network.GetFundedAccountInfo() @@ -59,33 +57,24 @@ func ValidatorChurn() { sentTeleporterMessage := sendEvent.Message // Construct the signed warp message - signedWarpMessageBytes := localUtils.ConstructSignedWarpMessageBytes(ctx, receipt, subnetAInfo, subnetBInfo) + signedWarpMessageBytes := network.ConstructSignedWarpMessageBytes(ctx, receipt, subnetAInfo, subnetBInfo) // // Modify the validator set on Subnet A // // Add new nodes to the validator set - log.Info("Adding nodes to the validator set") - startingNodeId := len(subnets)*5 + 1 - var nodesToAdd []string - for i := startingNodeId; i < startingNodeId+5; i++ { - n := fmt.Sprintf("node%d-bls", i) - nodesToAdd = append(nodesToAdd, n) - } - localUtils.AddSubnetValidators(ctx, subnetAInfo.SubnetID, nodesToAdd) + network.AddSubnetValidators(ctx, subnetAInfo.SubnetID, constructNodesToAddNames(network)) // Refresh the subnet info - subnets = network.GetSubnetsInfo() - subnetAInfo = subnets[0] - subnetBInfo = subnets[1] + subnetAInfo, subnetBInfo, _ = utils.GetThreeSubnets(network) // Trigger the proposer VM to update its height so that the inner VM can see the new validator set // We have to update all subnets, not just the ones directly involved in this test to ensure that the // proposer VM is updated on all subnets. - for _, subnetInfo := range subnets { + for _, subnetInfo := range network.GetSubnetsInfo() { err = subnetEvmUtils.IssueTxsToActivateProposerVMFork( - ctx, subnetInfo.ChainIDInt, fundedKey, subnetInfo.ChainWSClient, + ctx, subnetInfo.EVMChainID, fundedKey, subnetInfo.WSClient, ) Expect(err).Should(BeNil()) } @@ -117,7 +106,7 @@ func ValidatorChurn() { // Retry sending the message, and attempt to relay again. This should succeed. // log.Info("Retrying message sending on source chain") - optsA, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.ChainIDInt) + optsA, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.EVMChainID) Expect(err).Should(BeNil()) tx, err := subnetAInfo.TeleporterMessenger.RetrySendCrossChainMessage( optsA, subnetBInfo.BlockchainID, sentTeleporterMessage, @@ -125,7 +114,7 @@ func ValidatorChurn() { Expect(err).Should(BeNil()) // Wait for the transaction to be mined - receipt, err = bind.WaitMined(ctx, subnetAInfo.ChainRPCClient, tx) + receipt, err = bind.WaitMined(ctx, subnetAInfo.RPCClient, tx) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -142,3 +131,16 @@ func ValidatorChurn() { // If this changes in the future, this test will need to perform cleanup by removing the nodes that were added // and re-adding the nodes that were removed. } + +// Each subnet is assumed to have {nodesPerSubnet} nodes named nodeN-bls, where +// N is unique across each subnet. Nodes to be added should thus be named nodeN-bls +// where N starts one greater than the current total number of nodes. +func constructNodesToAddNames(network interfaces.Network) []string { + startingNodeId := len(network.GetSubnetsInfo())*nodesPerSubnet + 1 + var nodesToAdd []string + for i := startingNodeId; i < startingNodeId+newNodeCount; i++ { + n := fmt.Sprintf("node%d-bls", i) + nodesToAdd = append(nodesToAdd, n) + } + return nodesToAdd +} diff --git a/tests/interfaces/local_network.go b/tests/interfaces/local_network.go new file mode 100644 index 000000000..061660808 --- /dev/null +++ b/tests/interfaces/local_network.go @@ -0,0 +1,19 @@ +package interfaces + +import ( + "context" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/core/types" +) + +type LocalNetwork interface { + Network + AddSubnetValidators(ctx context.Context, subnetID ids.ID, nodeNames []string) + ConstructSignedWarpMessageBytes( + ctx context.Context, + sourceReceipt *types.Receipt, + source SubnetTestInfo, + destination SubnetTestInfo, + ) []byte +} diff --git a/tests/interfaces/network.go b/tests/interfaces/network.go new file mode 100644 index 000000000..4dea0f3a6 --- /dev/null +++ b/tests/interfaces/network.go @@ -0,0 +1,41 @@ +package interfaces + +import ( + "context" + "crypto/ecdsa" + + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ethereum/go-ethereum/common" +) + +// Defines the interface for the network setup functions used in the E2E tests +type Network interface { + // Returns all of the subnets support by this network. + GetSubnetsInfo() []SubnetTestInfo + + // Returns the Teleporter contract address for all subnets in this network. + GetTeleporterContractAddress() common.Address + + // An address and corresponding key that has native tokens on each of the subnets in this network. + GetFundedAccountInfo() (common.Address, *ecdsa.PrivateKey) + + // Whether or not the Avalanche network being used is available outside the scope of the test application. + IsExternalNetwork() bool + + // Whether or not the funded wallet is capable of relaying messages between subnets in this network. + // Intended to be true for local networks where all nodes are querable by the test application for their + // BLS signatures, and false for testnet networks where test application does not necessarily have + // connections with each validator. + SupportsIndependentRelaying() bool + + // For implementations where SupportsIndependentRelaying() is true, relays the specified message between the + // two subnets,and returns the receipt of the transaction the message was delivered in. + // For implementations where SupportsIndependentRelaying() is false, waits for the specific message to be relayed + // by an external relayer, and returns the receipt of the transaction the message was delivered in. + RelayMessage( + ctx context.Context, + sourceReceipt *types.Receipt, + source SubnetTestInfo, + destination SubnetTestInfo, + expectSuccess bool) *types.Receipt +} diff --git a/tests/interfaces/subnet_test_info.go b/tests/interfaces/subnet_test_info.go new file mode 100644 index 000000000..cbad393fc --- /dev/null +++ b/tests/interfaces/subnet_test_info.go @@ -0,0 +1,22 @@ +package interfaces + +import ( + "math/big" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/ethclient" + teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" + "github.com/ethereum/go-ethereum/common" +) + +// Tracks information about a test subnet used for executing tests against. +type SubnetTestInfo struct { + SubnetID ids.ID + BlockchainID ids.ID + NodeURIs []string + WSClient ethclient.Client + RPCClient ethclient.Client + EVMChainID *big.Int + TeleporterRegistryAddress common.Address + TeleporterMessenger *teleportermessenger.TeleporterMessenger +} diff --git a/tests/e2e_test.go b/tests/local/e2e_test.go similarity index 60% rename from tests/e2e_test.go rename to tests/local/e2e_test.go index 799257846..a27395764 100644 --- a/tests/e2e_test.go +++ b/tests/local/e2e_test.go @@ -1,14 +1,13 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package tests +package local import ( "os" "testing" - "github.com/ava-labs/teleporter/tests/network" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" + "github.com/ava-labs/teleporter/tests/flows" deploymentUtils "github.com/ava-labs/teleporter/utils/deployment-utils" "github.com/ethereum/go-ethereum/log" "github.com/onsi/ginkgo/v2" @@ -20,6 +19,10 @@ const ( warpGenesisFile = "./tests/utils/warp-genesis.json" ) +var ( + localNetworkInstance *localNetwork +) + func TestE2E(t *testing.T) { if os.Getenv("RUN_E2E") == "" { t.Skip("Environment variable RUN_E2E not set; skipping E2E tests") @@ -31,68 +34,78 @@ func TestE2E(t *testing.T) { // Define the Teleporter before and after suite functions. var _ = ginkgo.BeforeSuite(func() { - localUtils.SetupNetwork(warpGenesisFile) + localNetworkInstance = newLocalNetwork(warpGenesisFile) // Generate the Teleporter deployment values teleporterDeployerTransaction, teleporterDeployerAddress, teleporterContractAddress, err := deploymentUtils.ConstructKeylessTransaction(teleporterByteCodeFile, false) Expect(err).Should(BeNil()) - _, fundedKey := localUtils.GetFundedAccountInfo() - localUtils.DeployTeleporterContracts( + _, fundedKey := localNetworkInstance.GetFundedAccountInfo() + localNetworkInstance.deployTeleporterContracts( teleporterDeployerTransaction, teleporterDeployerAddress, teleporterContractAddress, fundedKey, ) - localUtils.DeployTeleporterRegistryContracts(teleporterContractAddress, fundedKey) + localNetworkInstance.deployTeleporterRegistryContracts(teleporterContractAddress, fundedKey) log.Info("Set up ginkgo before suite") }) -var _ = ginkgo.AfterSuite(localUtils.TearDownNetwork) +var _ = ginkgo.AfterSuite(func() { + localNetworkInstance.tearDownNetwork() +}) var _ = ginkgo.Describe("[Teleporter integration tests]", func() { // Cross-chain application tests - ginkgo.It("Example cross chain messenger", ExampleMessengerGinkgo) - ginkgo.It("ERC20 bridge multihop", ERC20BridgeMultihopGinkgo) + ginkgo.It("Example cross chain messenger", func() { + flows.ExampleMessenger(localNetworkInstance) + }) + ginkgo.It("ERC20 bridge multihop", func() { + flows.ERC20BridgeMultihop(localNetworkInstance) + }) // Teleporter tests ginkgo.It("Send a message from Subnet A to Subnet B, and one from B to A", func() { - BasicSendReceive(&network.LocalNetwork{}) + flows.BasicSendReceive(localNetworkInstance) }) ginkgo.It("Deliver to the wrong chain", func() { - DeliverToWrongChain(&network.LocalNetwork{}) + flows.DeliverToWrongChain(localNetworkInstance) }) ginkgo.It("Deliver to non-existent contract", func() { - DeliverToNonExistentContract(&network.LocalNetwork{}) + flows.DeliverToNonExistentContract(localNetworkInstance) }) ginkgo.It("Retry successful execution", func() { - RetrySuccessfulExecution(&network.LocalNetwork{}) + flows.RetrySuccessfulExecution(localNetworkInstance) }) ginkgo.It("Unallowed relayer", func() { - UnallowedRelayer(&network.LocalNetwork{}) + flows.UnallowedRelayer(localNetworkInstance) }) - ginkgo.It("Receive message twice", func() { - ReceiveMessageTwice(&network.LocalNetwork{}) + ginkgo.It("Relay message twice", func() { + flows.RelayMessageTwice(localNetworkInstance) }) ginkgo.It("Add additional fee amount", func() { - AddFeeAmount(&network.LocalNetwork{}) + flows.AddFeeAmount(localNetworkInstance) }) ginkgo.It("Send specific receipts", func() { - SendSpecificReceipts(&network.LocalNetwork{}) + flows.SendSpecificReceipts(localNetworkInstance) }) ginkgo.It("Insufficient gas", func() { - InsufficientGas(&network.LocalNetwork{}) + flows.InsufficientGas(localNetworkInstance) }) ginkgo.It("Resubmit altered message", func() { - ResubmitAlteredMessage(&network.LocalNetwork{}) + flows.ResubmitAlteredMessage(localNetworkInstance) }) ginkgo.It("Block hash publish and receive", func() { - BlockHashPublishReceive(&network.LocalNetwork{}) + flows.BlockHashPublishReceive(localNetworkInstance) }) // The following tests require special behavior by the relayer, so we only run them on a local network - ginkgo.It("Relayer modifies message", RelayerModifiesMessage) - ginkgo.It("Validator churn", ValidatorChurn) + ginkgo.It("Relayer modifies message", func() { + flows.RelayerModifiesMessage(localNetworkInstance) + }) + ginkgo.It("Validator churn", func() { + flows.ValidatorChurn(localNetworkInstance) + }) // Since the validator churn test modifies the network topology, we put it last for now. // It should not affect the other tests, but we get some errors if we run it before the other tests. // TODO: we should fix this so that the order of the tests does not matter. diff --git a/tests/local/network.go b/tests/local/network.go new file mode 100644 index 000000000..7f02bb5cc --- /dev/null +++ b/tests/local/network.go @@ -0,0 +1,468 @@ +package local + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "os" + "time" + + runner_sdk "github.com/ava-labs/avalanche-network-runner/client" + "github.com/ava-labs/avalanche-network-runner/rpcpb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/ethclient" + subnetEvmInterfaces "github.com/ava-labs/subnet-evm/interfaces" + "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm" + "github.com/ava-labs/subnet-evm/rpc" + "github.com/ava-labs/subnet-evm/tests/utils/runner" + warpBackend "github.com/ava-labs/subnet-evm/warp" + "github.com/ava-labs/subnet-evm/x/warp" + teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" + teleporterregistry "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/upgrades/TeleporterRegistry" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + . "github.com/onsi/gomega" +) + +var _ interfaces.LocalNetwork = &localNetwork{} + +// Implements Network, pointing to the network setup in local_network_setup.go +type localNetwork struct { + teleporterContractAddress common.Address + subnetAID, subnetBID, subnetCID ids.ID + subnetsInfo map[ids.ID]*interfaces.SubnetTestInfo + subnetNodeNames map[ids.ID][]string + + globalFundedKey *ecdsa.PrivateKey + + // Internal vars only used to set up the local network + anrClient runner_sdk.Client + manager *runner.NetworkManager + warpChainConfigPath string +} + +const ( + fundedKeyStr = "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027" +) + +func newLocalNetwork(warpGenesisFile string) *localNetwork { + ctx := context.Background() + var err error + + // Name 10 new validators (which should have BLS key registered) + var subnetANodeNames []string + var subnetBNodeNames []string + var subnetCNodeNames []string + for i := 1; i <= 15; i++ { + n := fmt.Sprintf("node%d-bls", i) + if i <= 5 { + subnetANodeNames = append(subnetANodeNames, n) + } else if i <= 10 { + subnetBNodeNames = append(subnetBNodeNames, n) + } else { + subnetCNodeNames = append(subnetCNodeNames, n) + } + } + + f, err := os.CreateTemp(os.TempDir(), "config.json") + Expect(err).Should(BeNil()) + _, err = f.Write([]byte(`{"warp-api-enabled": true}`)) + Expect(err).Should(BeNil()) + warpChainConfigPath := f.Name() + + // Make sure that the warp genesis file exists + _, err = os.Stat(warpGenesisFile) + Expect(err).Should(BeNil()) + + anrConfig := runner.NewDefaultANRConfig() + log.Info("dbg", "anrConfig", anrConfig) + manager := runner.NewNetworkManager(anrConfig) + + // Construct the network using the avalanche-network-runner + _, err = manager.StartDefaultNetwork(ctx) + Expect(err).Should(BeNil()) + err = manager.SetupNetwork( + ctx, + anrConfig.AvalancheGoExecPath, + []*rpcpb.BlockchainSpec{ + { + VmName: evm.IDStr, + Genesis: warpGenesisFile, + ChainConfig: warpChainConfigPath, + SubnetSpec: &rpcpb.SubnetSpec{ + SubnetConfig: "", + Participants: subnetANodeNames, + }, + }, + { + VmName: evm.IDStr, + Genesis: warpGenesisFile, + ChainConfig: warpChainConfigPath, + SubnetSpec: &rpcpb.SubnetSpec{ + SubnetConfig: "", + Participants: subnetBNodeNames, + }, + }, + { + VmName: evm.IDStr, + Genesis: warpGenesisFile, + ChainConfig: warpChainConfigPath, + SubnetSpec: &rpcpb.SubnetSpec{ + SubnetConfig: "", + Participants: subnetCNodeNames, + }, + }, + }, + ) + Expect(err).Should(BeNil()) + + // Issue transactions to activate the proposerVM fork on the chains + globalFundedKey, err := crypto.HexToECDSA(fundedKeyStr) + Expect(err).Should(BeNil()) + setupProposerVM(ctx, globalFundedKey, manager, 0) + setupProposerVM(ctx, globalFundedKey, manager, 1) + setupProposerVM(ctx, globalFundedKey, manager, 2) + + // Create the ANR client + logLevel, err := logging.ToLevel("info") + Expect(err).Should(BeNil()) + + logFactory := logging.NewFactory(logging.Config{ + DisplayLevel: logLevel, + LogLevel: logLevel, + }) + zapLog, err := logFactory.Make("main") + Expect(err).Should(BeNil()) + + anrClient, err := runner_sdk.New(runner_sdk.Config{ + Endpoint: "0.0.0.0:12352", + DialTimeout: 10 * time.Second, + }, zapLog) + Expect(err).Should(BeNil()) + + // On initial startup, we need to first set the subnet node names + // before calling setSubnetValues for the two subnets + subnetIDs := manager.GetSubnets() + Expect(len(subnetIDs)).Should(Equal(3)) + subnetAID := subnetIDs[0] + subnetBID := subnetIDs[1] + subnetCID := subnetIDs[2] + + res := &localNetwork{ + subnetAID: subnetAID, + subnetBID: subnetBID, + subnetCID: subnetCID, + subnetsInfo: make(map[ids.ID]*interfaces.SubnetTestInfo), + subnetNodeNames: map[ids.ID][]string{ + subnetAID: subnetANodeNames, + subnetBID: subnetBNodeNames, + subnetCID: subnetCNodeNames, + }, + globalFundedKey: globalFundedKey, + anrClient: anrClient, + manager: manager, + warpChainConfigPath: warpChainConfigPath, + } + res.setSubnetValues(subnetAID) + res.setSubnetValues(subnetBID) + res.setSubnetValues(subnetCID) + return res +} + +func (n *localNetwork) setSubnetValues(subnetID ids.ID) { + subnetDetails, ok := n.manager.GetSubnet(subnetID) + Expect(ok).Should(BeTrue()) + blockchainID := subnetDetails.BlockchainID + + // Reset the validator URIs, as they may have changed + subnetDetails.ValidatorURIs = nil + status, err := n.anrClient.Status(context.Background()) + Expect(err).Should(BeNil()) + nodeInfos := status.GetClusterInfo().GetNodeInfos() + + for _, nodeName := range n.subnetNodeNames[subnetID] { + subnetDetails.ValidatorURIs = append(subnetDetails.ValidatorURIs, nodeInfos[nodeName].Uri) + } + var chainNodeURIs []string + chainNodeURIs = append(chainNodeURIs, subnetDetails.ValidatorURIs...) + + chainWSURI := utils.HttpToWebsocketURI(chainNodeURIs[0], blockchainID.String()) + chainRPCURI := utils.HttpToRPCURI(chainNodeURIs[0], blockchainID.String()) + + if n.subnetsInfo[subnetID] != nil && n.subnetsInfo[subnetID].WSClient != nil { + n.subnetsInfo[subnetID].WSClient.Close() + } + chainWSClient, err := ethclient.Dial(chainWSURI) + Expect(err).Should(BeNil()) + if n.subnetsInfo[subnetID] != nil && n.subnetsInfo[subnetID].RPCClient != nil { + n.subnetsInfo[subnetID].RPCClient.Close() + } + chainRPCClient, err := ethclient.Dial(chainRPCURI) + Expect(err).Should(BeNil()) + chainIDInt, err := chainRPCClient.ChainID(context.Background()) + Expect(err).Should(BeNil()) + + // Set the new values in the subnetsInfo map + if n.subnetsInfo[subnetID] == nil { + n.subnetsInfo[subnetID] = &interfaces.SubnetTestInfo{} + } + n.subnetsInfo[subnetID].SubnetID = subnetID + n.subnetsInfo[subnetID].BlockchainID = blockchainID + n.subnetsInfo[subnetID].NodeURIs = chainNodeURIs + n.subnetsInfo[subnetID].WSClient = chainWSClient + n.subnetsInfo[subnetID].RPCClient = chainRPCClient + n.subnetsInfo[subnetID].EVMChainID = chainIDInt + + // TeleporterMessenger is set in DeployTeleporterContracts + // TeleporterRegistryAddress is set in DeployTeleporterRegistryContracts +} + +// deployTeleporterContracts deploys the Teleporter contract to all subnets. +// The caller is responsible for generating the deployment transaction information +func (n *localNetwork) deployTeleporterContracts( + transactionBytes []byte, + deployerAddress common.Address, + contractAddress common.Address, + fundedKey *ecdsa.PrivateKey, +) { + log.Info("Deploying Teleporter contract to subnets") + + subnetsInfoList := n.GetSubnetsInfo() + + // Set the package level teleporterContractAddress + n.teleporterContractAddress = contractAddress + + ctx := context.Background() + + for _, subnetInfo := range subnetsInfoList { + // Fund the deployer address + { + fundAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth + fundDeployerTx := utils.CreateNativeTransferTransaction( + ctx, subnetInfo, fundedKey, deployerAddress, fundAmount, + ) + utils.SendTransactionAndWaitForAcceptance(ctx, subnetInfo, fundDeployerTx, true) + } + log.Info("Finished funding Teleporter deployer", "blockchainID", subnetInfo.BlockchainID.Hex()) + + // Deploy Teleporter contract + { + rpcClient, err := rpc.DialContext( + ctx, + utils.HttpToRPCURI(subnetInfo.NodeURIs[0], subnetInfo.BlockchainID.String()), + ) + Expect(err).Should(BeNil()) + defer rpcClient.Close() + + newHeads := make(chan *types.Header, 10) + sub, err := subnetInfo.WSClient.SubscribeNewHead(ctx, newHeads) + Expect(err).Should(BeNil()) + defer sub.Unsubscribe() + + err = rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(transactionBytes)) + Expect(err).Should(BeNil()) + + <-newHeads + teleporterCode, err := subnetInfo.RPCClient.CodeAt(ctx, n.teleporterContractAddress, nil) + Expect(err).Should(BeNil()) + Expect(len(teleporterCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode + } + teleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( + n.teleporterContractAddress, subnetInfo.RPCClient, + ) + Expect(err).Should(BeNil()) + n.subnetsInfo[subnetInfo.SubnetID].TeleporterMessenger = teleporterMessenger + log.Info("Finished deploying Teleporter contract", "blockchainID", subnetInfo.BlockchainID.Hex()) + } + log.Info("Deployed Teleporter contracts to all subnets") +} + +func (n *localNetwork) deployTeleporterRegistryContracts( + teleporterAddress common.Address, + deployerKey *ecdsa.PrivateKey, +) { + log.Info("Deploying TeleporterRegistry contract to subnets") + ctx := context.Background() + + entries := []teleporterregistry.ProtocolRegistryEntry{ + { + Version: big.NewInt(1), + ProtocolAddress: teleporterAddress, + }, + } + + for _, subnetInfo := range n.GetSubnetsInfo() { + opts, err := bind.NewKeyedTransactorWithChainID(deployerKey, subnetInfo.EVMChainID) + Expect(err).Should(BeNil()) + teleporterRegistryAddress, tx, _, err := teleporterregistry.DeployTeleporterRegistry( + opts, subnetInfo.RPCClient, entries, + ) + Expect(err).Should(BeNil()) + + n.subnetsInfo[subnetInfo.SubnetID].TeleporterRegistryAddress = teleporterRegistryAddress + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, subnetInfo.RPCClient, tx) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + log.Info("Deployed TeleporterRegistry contract to subnet", subnetInfo.SubnetID.Hex(), + "Deploy address", teleporterRegistryAddress.Hex()) + } + + log.Info("Deployed TeleporterRegistry contracts to all subnets") +} + +func (n *localNetwork) GetSubnetsInfo() []interfaces.SubnetTestInfo { + return []interfaces.SubnetTestInfo{ + *n.subnetsInfo[n.subnetAID], + *n.subnetsInfo[n.subnetBID], + *n.subnetsInfo[n.subnetCID], + } +} + +func (n *localNetwork) GetTeleporterContractAddress() common.Address { + return n.teleporterContractAddress +} + +func (n *localNetwork) GetFundedAccountInfo() (common.Address, *ecdsa.PrivateKey) { + fundedAddress := crypto.PubkeyToAddress(n.globalFundedKey.PublicKey) + return fundedAddress, n.globalFundedKey +} + +func (n *localNetwork) IsExternalNetwork() bool { + return false +} + +func (n *localNetwork) SupportsIndependentRelaying() bool { + // Messages can be relayed by the test application for local + // networks with connections to each node. + return true +} + +func (n *localNetwork) RelayMessage(ctx context.Context, + sourceReceipt *types.Receipt, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, + expectSuccess bool) *types.Receipt { + // Fetch the Teleporter message from the logs + sendEvent, err := + utils.GetEventFromLogs(sourceReceipt.Logs, source.TeleporterMessenger.ParseSendCrossChainMessage) + Expect(err).Should(BeNil()) + + signedWarpMessageBytes := n.ConstructSignedWarpMessageBytes(ctx, sourceReceipt, source, destination) + + // Construct the transaction to send the Warp message to the destination chain + signedTx := utils.CreateReceiveCrossChainMessageTransaction( + ctx, + signedWarpMessageBytes, + sendEvent.Message.RequiredGasLimit, + n.teleporterContractAddress, + n.globalFundedKey, + destination, + ) + + log.Info("Sending transaction to destination chain") + receipt := utils.SendTransactionAndWaitForAcceptance(ctx, destination, signedTx, expectSuccess) + + if !expectSuccess { + return nil + } + + // Check the transaction logs for the ReceiveCrossChainMessage event emitted by the Teleporter contract + receiveEvent, err := + utils.GetEventFromLogs(receipt.Logs, destination.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) + Expect(receiveEvent.OriginBlockchainID[:]).Should(Equal(source.BlockchainID[:])) + return receipt +} + +func (n *localNetwork) setAllSubnetValues() { + subnetIDs := n.manager.GetSubnets() + Expect(len(subnetIDs)).Should(Equal(3)) + + n.subnetAID = subnetIDs[0] + n.setSubnetValues(n.subnetAID) + + n.subnetBID = subnetIDs[1] + n.setSubnetValues(n.subnetBID) + + n.subnetCID = subnetIDs[2] + n.setSubnetValues(n.subnetCID) +} + +func (n *localNetwork) tearDownNetwork() { + log.Info("Tearing down network") + Expect(n.manager).ShouldNot(BeNil()) + Expect(n.manager.TeardownNetwork()).Should(BeNil()) + Expect(os.Remove(n.warpChainConfigPath)).Should(BeNil()) +} + +func (n *localNetwork) AddSubnetValidators(ctx context.Context, subnetID ids.ID, nodeNames []string) { + _, err := n.anrClient.AddSubnetValidators(ctx, []*rpcpb.SubnetValidatorsSpec{ + { + SubnetId: subnetID.String(), + NodeNames: nodeNames, + }, + }) + Expect(err).Should(BeNil()) + + // Add the new node names + n.subnetNodeNames[subnetID] = append(n.subnetNodeNames[subnetID], nodeNames...) + + n.setAllSubnetValues() +} + +func (n *localNetwork) ConstructSignedWarpMessageBytes( + ctx context.Context, + sourceReceipt *types.Receipt, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, +) []byte { + log.Info("Fetching relevant warp logs from the newly produced block") + logs, err := source.RPCClient.FilterLogs(ctx, subnetEvmInterfaces.FilterQuery{ + BlockHash: &sourceReceipt.BlockHash, + Addresses: []common.Address{warp.Module.Address}, + }) + Expect(err).Should(BeNil()) + Expect(len(logs)).Should(Equal(1)) + + // Check for relevant warp log from subscription and ensure that it matches + // the log extracted from the last block. + txLog := logs[0] + log.Info("Parsing logData as unsigned warp message") + unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data) + Expect(err).Should(BeNil()) + + // Set local variables for the duration of the test + unsignedWarpMessageID := unsignedMsg.ID() + unsignedWarpMsg := unsignedMsg + log.Info( + "Parsed unsignedWarpMsg", + "unsignedWarpMessageID", unsignedWarpMessageID, + "unsignedWarpMessage", unsignedWarpMsg, + ) + + // Loop over each client on chain A to ensure they all have time to accept the block. + // Note: if we did not confirm this here, the next stage could be racy since it assumes every node + // has accepted the block. + waitForAllValidatorsToAcceptBlock(ctx, source.NodeURIs, source.BlockchainID, sourceReceipt.BlockNumber.Uint64()) + + // Get the aggregate signature for the Warp message + log.Info("Fetching aggregate signature from the source chain validators") + warpClient, err := warpBackend.NewClient(source.NodeURIs[0], source.BlockchainID.String()) + Expect(err).Should(BeNil()) + signedWarpMessageBytes, err := warpClient.GetMessageAggregateSignature( + ctx, unsignedWarpMessageID, params.WarpQuorumDenominator, + ) + Expect(err).Should(BeNil()) + + return signedWarpMessageBytes +} diff --git a/tests/local/network_utils.go b/tests/local/network_utils.go new file mode 100644 index 000000000..f25c099e6 --- /dev/null +++ b/tests/local/network_utils.go @@ -0,0 +1,57 @@ +package local + +import ( + "context" + "crypto/ecdsa" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/ethclient" + subnetEvmUtils "github.com/ava-labs/subnet-evm/tests/utils" + "github.com/ava-labs/subnet-evm/tests/utils/runner" + "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/log" + + . "github.com/onsi/gomega" +) + +// Issues txs to activate the proposer VM fork on the specified subnet index in the manager +func setupProposerVM(ctx context.Context, fundedKey *ecdsa.PrivateKey, manager *runner.NetworkManager, index int) { + subnet := manager.GetSubnets()[index] + subnetDetails, ok := manager.GetSubnet(subnet) + Expect(ok).Should(BeTrue()) + + chainID := subnetDetails.BlockchainID + uri := utils.HttpToWebsocketURI(subnetDetails.ValidatorURIs[0], chainID.String()) + + client, err := ethclient.Dial(uri) + Expect(err).Should(BeNil()) + chainIDInt, err := client.ChainID(ctx) + Expect(err).Should(BeNil()) + + err = subnetEvmUtils.IssueTxsToActivateProposerVMFork(ctx, chainIDInt, fundedKey, client) + Expect(err).Should(BeNil()) +} + +// Blocks until all validators specified in nodeURIs have reached the specified block height +func waitForAllValidatorsToAcceptBlock(ctx context.Context, nodeURIs []string, blockchainID ids.ID, height uint64) { + cctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + for i, uri := range nodeURIs { + chainAWSURI := utils.HttpToWebsocketURI(uri, blockchainID.String()) + log.Info("Creating ethclient for blockchain", "blockchainID", blockchainID.String(), "wsURI", chainAWSURI) + client, err := ethclient.Dial(chainAWSURI) + Expect(err).Should(BeNil()) + defer client.Close() + + // Loop until each node has advanced to >= the height of the block that emitted the warp log + for { + block, err := client.BlockByNumber(cctx, nil) + Expect(err).Should(BeNil()) + if block.NumberU64() >= height { + log.Info("client accepted the block containing SendWarpMessage", "client", i, "height", block.NumberU64()) + break + } + } + } +} diff --git a/tests/network/example_fuji_network.go b/tests/network/example_fuji_network.go deleted file mode 100644 index c1c8ee0f1..000000000 --- a/tests/network/example_fuji_network.go +++ /dev/null @@ -1,245 +0,0 @@ -package network - -import ( - "context" - "crypto/ecdsa" - "strings" - "time" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/interfaces" - teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - . "github.com/onsi/gomega" -) - -var _ Network = &FujiNetwork{} - -// Amplify, Bulletin, Conduit subnet constants -// The integration tests are currently not working against these subnets due to -// incompatibility with the subnet-evm version deployed on them. -var ( - teleporterContractAddress = common.HexToAddress("0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635") - - amplifySubnetIDStr = "2PsShLjrFFwR51DMcAh8pyuwzLn1Ym3zRhuXLTmLCR1STk2mL6" - amplifyBlockchainIDStr = "2nFUad4Nw4pCgEF6MwYgGuKrzKbHJzM8wF29jeVUL41RWHgNRa" - amplifyWSURI = "wss://subnets.avax.network/amplify/testnet/ws" - amplifyRPCURI = "https://subnets.avax.network/amplify/testnet/rpc" - amplifySubnetID ids.ID - amplifyBlockchainID ids.ID - amplifyTeleporterRegistryAddress common.Address // Empty for now - amplifyTeleporterMessenger *teleportermessenger.TeleporterMessenger - - bulletinSubnetIDStr = "cbXsFGWSDWUYTmRXUoCirVDdQkZmUWrkQQYoVc2wUoDm8eFup" - bulletinBlockchainIDStr = "2e3RJ3ub9Pceh8fJ3HX3gZ6nSXJLvBJ9WoXLcU4nwdpZ8X2RLq" - bulletinWSURI = "wss://subnets.avax.network/bulletin/testnet/ws" - bulletinRPCURI = "https://subnets.avax.network/bulletin/testnet/rpc" - bulletinSubnetID ids.ID - bulletinBlockchainID ids.ID - bulletinTeleporterRegistryAddress common.Address // Empty for now - bulletinTeleporterMessenger *teleportermessenger.TeleporterMessenger - - conduitSubnetIDStr = "wW7JVmjXp8SKrpacGzM81RBXdfcLDVY6M2DkFyArEXgtkyozK" - conduitBlockchainIDStr = "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75" - conduitWSURI = "wss://subnets.avax.network/conduit/testnet/ws" - conduitRPCURI = "https://subnets.avax.network/conduit/testnet/rpc" - conduitSubnetID ids.ID - conduitBlockchainID ids.ID - conduitTeleporterRegistryAddress common.Address // Empty for now - conduitTeleporterMessenger *teleportermessenger.TeleporterMessenger - - skHex = strings.TrimPrefix("", "0x") // To be supplied by user -) - -func init() { - var err error - - amplifySubnetID, err = ids.FromString(amplifySubnetIDStr) - if err != nil { - panic(err) - } - amplifyBlockchainID, err = ids.FromString(amplifyBlockchainIDStr) - if err != nil { - panic(err) - } - - bulletinSubnetID, err = ids.FromString(bulletinSubnetIDStr) - if err != nil { - panic(err) - } - bulletinBlockchainID, err = ids.FromString(bulletinBlockchainIDStr) - if err != nil { - panic(err) - } - - conduitSubnetID, err = ids.FromString(conduitSubnetIDStr) - if err != nil { - panic(err) - } - conduitBlockchainID, err = ids.FromString(conduitBlockchainIDStr) - if err != nil { - panic(err) - } -} - -// Implements Network, pointing to subnets deployed on Fuji -type FujiNetwork struct { - amplifyInfo utils.SubnetTestInfo - bulletinInfo utils.SubnetTestInfo - conduitInfo utils.SubnetTestInfo -} - -func NewFujiNetwork() *FujiNetwork { - amplifyWSClient, err := ethclient.Dial(amplifyWSURI) - Expect(err).Should(BeNil()) - amplifyRPCClient, err := ethclient.Dial(amplifyRPCURI) - Expect(err).Should(BeNil()) - amplifyChainIDInt, err := amplifyRPCClient.ChainID(context.Background()) - Expect(err).Should(BeNil()) - amplifyTeleporterMessenger, err = teleportermessenger.NewTeleporterMessenger( - teleporterContractAddress, amplifyRPCClient, - ) - Expect(err).Should(BeNil()) - - bulletinWSClient, err := ethclient.Dial(bulletinWSURI) - Expect(err).Should(BeNil()) - bulletinRPCClient, err := ethclient.Dial(bulletinRPCURI) - Expect(err).Should(BeNil()) - bulletinChainIDInt, err := bulletinRPCClient.ChainID(context.Background()) - Expect(err).Should(BeNil()) - bulletinTeleporterMessenger, err = teleportermessenger.NewTeleporterMessenger( - teleporterContractAddress, bulletinRPCClient, - ) - Expect(err).Should(BeNil()) - - conduitWSClient, err := ethclient.Dial(conduitWSURI) - Expect(err).Should(BeNil()) - conduitRPCClient, err := ethclient.Dial(conduitRPCURI) - Expect(err).Should(BeNil()) - conduitChainIDInt, err := conduitRPCClient.ChainID(context.Background()) - Expect(err).Should(BeNil()) - conduitTeleporterMessenger, err = teleportermessenger.NewTeleporterMessenger( - teleporterContractAddress, conduitRPCClient, - ) - Expect(err).Should(BeNil()) - - return &FujiNetwork{ - amplifyInfo: utils.SubnetTestInfo{ - SubnetID: amplifySubnetID, - BlockchainID: amplifyBlockchainID, - ChainIDInt: amplifyChainIDInt, - ChainWSClient: amplifyWSClient, - ChainRPCClient: amplifyRPCClient, - TeleporterRegistryAddress: amplifyTeleporterRegistryAddress, - TeleporterMessenger: amplifyTeleporterMessenger, - }, - bulletinInfo: utils.SubnetTestInfo{ - SubnetID: bulletinSubnetID, - BlockchainID: bulletinBlockchainID, - ChainIDInt: bulletinChainIDInt, - ChainWSClient: bulletinWSClient, - ChainRPCClient: bulletinRPCClient, - TeleporterRegistryAddress: bulletinTeleporterRegistryAddress, - TeleporterMessenger: bulletinTeleporterMessenger, - }, - conduitInfo: utils.SubnetTestInfo{ - SubnetID: conduitSubnetID, - BlockchainID: conduitBlockchainID, - ChainIDInt: conduitChainIDInt, - ChainWSClient: conduitWSClient, - ChainRPCClient: conduitRPCClient, - TeleporterRegistryAddress: conduitTeleporterRegistryAddress, - TeleporterMessenger: conduitTeleporterMessenger, - }, - } -} - -func (n *FujiNetwork) CloseNetworkConnections() { - n.amplifyInfo.ChainWSClient.Close() - n.amplifyInfo.ChainRPCClient.Close() - n.bulletinInfo.ChainWSClient.Close() - n.bulletinInfo.ChainRPCClient.Close() - n.conduitInfo.ChainWSClient.Close() - n.conduitInfo.ChainRPCClient.Close() -} - -func (n *FujiNetwork) GetSubnetsInfo() []utils.SubnetTestInfo { - return []utils.SubnetTestInfo{ - n.amplifyInfo, - n.bulletinInfo, - n.conduitInfo, - } -} - -func (n *FujiNetwork) GetTeleporterContractAddress() common.Address { - return teleporterContractAddress -} - -func (n *FujiNetwork) GetFundedAccountInfo() (common.Address, *ecdsa.PrivateKey) { - key, err := crypto.HexToECDSA(skHex) - Expect(err).Should(BeNil()) - userAddress := crypto.PubkeyToAddress(key.PublicKey) - - return userAddress, key -} - -func (n *FujiNetwork) RelayMessage(ctx context.Context, - sourceReceipt *types.Receipt, - source utils.SubnetTestInfo, - destination utils.SubnetTestInfo, - expectSuccess bool) *types.Receipt { - // Set the context to expire after 20 seconds - var cancel context.CancelFunc - cctx, cancel := context.WithTimeout(ctx, 20*time.Second) - defer cancel() - - // Get the Teleporter message ID from the receipt - sendEvent, err := utils.GetEventFromLogs( - sourceReceipt.Logs, source.TeleporterMessenger.ParseSendCrossChainMessage, - ) - Expect(err).Should(BeNil()) - Expect(sendEvent.DestinationBlockchainID[:]).Should(Equal(destination.BlockchainID[:])) - - teleporterMessageID := sendEvent.Message.MessageID - - // Block until the message with the corresponding Teleporter message ID is received on the destination chain - newHeads := make(chan *types.Header, 10) - sub, err := destination.ChainWSClient.SubscribeNewHead(cctx, newHeads) - Expect(err).Should(BeNil()) - defer sub.Unsubscribe() - - select { - case <-cctx.Done(): - log.Error("Message was not relayed in time") - Expect(true).Should(BeFalse()) - case head := <-newHeads: - hash := head.Hash() - logs, err := destination.ChainRPCClient.FilterLogs(cctx, interfaces.FilterQuery{ - BlockHash: &hash, - Addresses: []common.Address{teleporterContractAddress}, - }) - Expect(err).Should(BeNil()) - if len(logs) > 0 { - var l []*types.Log - for _, log := range logs { - l = append(l, &log) - } - - receiveEvent, err := utils.GetEventFromLogs(l, destination.TeleporterMessenger.ParseReceiveCrossChainMessage) - Expect(err).Should(BeNil()) - if receiveEvent.MessageID.Cmp(teleporterMessageID) == 0 { - receipt, err := destination.ChainRPCClient.TransactionReceipt(cctx, receiveEvent.Raw.TxHash) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - return receipt - } - } - } - - return nil -} diff --git a/tests/network/local_network.go b/tests/network/local_network.go deleted file mode 100644 index 26355db59..000000000 --- a/tests/network/local_network.go +++ /dev/null @@ -1,36 +0,0 @@ -package network - -import ( - "context" - "crypto/ecdsa" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" - "github.com/ethereum/go-ethereum/common" -) - -var _ Network = &LocalNetwork{} - -// Implements Network, pointing to the network setup in local_network_setup.go -type LocalNetwork struct{} - -func (n *LocalNetwork) GetSubnetsInfo() []utils.SubnetTestInfo { - return localUtils.GetSubnetsInfo() -} - -func (n *LocalNetwork) GetTeleporterContractAddress() common.Address { - return localUtils.GetTeleporterContractAddress() -} - -func (n *LocalNetwork) GetFundedAccountInfo() (common.Address, *ecdsa.PrivateKey) { - return localUtils.GetFundedAccountInfo() -} - -func (n *LocalNetwork) RelayMessage(ctx context.Context, - sourceReceipt *types.Receipt, - source utils.SubnetTestInfo, - destination utils.SubnetTestInfo, - expectSuccess bool) *types.Receipt { - return localUtils.RelayMessage(ctx, sourceReceipt, source, destination, expectSuccess) -} diff --git a/tests/network/network.go b/tests/network/network.go deleted file mode 100644 index 4d6593c89..000000000 --- a/tests/network/network.go +++ /dev/null @@ -1,22 +0,0 @@ -package network - -import ( - "context" - "crypto/ecdsa" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/teleporter/tests/utils" - "github.com/ethereum/go-ethereum/common" -) - -// Defines the interface for the network setup functions used in the E2E tests -type Network interface { - GetSubnetsInfo() []utils.SubnetTestInfo - GetTeleporterContractAddress() common.Address - GetFundedAccountInfo() (common.Address, *ecdsa.PrivateKey) - RelayMessage(ctx context.Context, - sourceReceipt *types.Receipt, - source utils.SubnetTestInfo, - destination utils.SubnetTestInfo, - expectSuccess bool) *types.Receipt -} diff --git a/tests/send_specific_receipts.go b/tests/send_specific_receipts.go deleted file mode 100644 index 828c1cbc4..000000000 --- a/tests/send_specific_receipts.go +++ /dev/null @@ -1,148 +0,0 @@ -package tests - -import ( - "context" - "math/big" - - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - "github.com/ava-labs/teleporter/tests/network" - "github.com/ava-labs/teleporter/tests/utils" - localUtils "github.com/ava-labs/teleporter/tests/utils/local-network-utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - . "github.com/onsi/gomega" -) - -func SendSpecificReceipts(network network.Network) { - subnets := network.GetSubnetsInfo() - Expect(len(subnets)).Should(BeNumerically(">=", 2)) - subnetAInfo := subnets[0] - subnetBInfo := subnets[1] - teleporterContractAddress := network.GetTeleporterContractAddress() - fundedAddress, fundedKey := network.GetFundedAccountInfo() - ctx := context.Background() - - // Use mock token as the fee token - mockTokenAddress, mockToken := localUtils.DeployExampleERC20( - context.Background(), fundedKey, subnetAInfo, - ) - localUtils.ExampleERC20Approve( - ctx, - mockToken, - teleporterContractAddress, - big.NewInt(0).Mul(big.NewInt(1e18), - big.NewInt(10)), - subnetAInfo, - fundedKey, - ) - - relayerFeePerMessage := big.NewInt(5) - totalAccumulatedRelayerFee := big.NewInt(10) - - destinationKey, err := crypto.GenerateKey() - Expect(err).Should(BeNil()) - destinationAddress := crypto.PubkeyToAddress(destinationKey.PublicKey) - - // Send two messages from Subnet A to Subnet B - sendCrossChainMessageInput := teleportermessenger.TeleporterMessageInput{ - DestinationBlockchainID: subnetBInfo.BlockchainID, - DestinationAddress: destinationAddress, - FeeInfo: teleportermessenger.TeleporterFeeInfo{ - FeeTokenAddress: mockTokenAddress, - Amount: relayerFeePerMessage, - }, - RequiredGasLimit: big.NewInt(1), - AllowedRelayerAddresses: []common.Address{}, - Message: []byte{1, 2, 3, 4}, - } - - // Send first message from Subnet A to Subnet B with fee amount 5 - sendCrossChainMsgReceipt, messageID1 := utils.SendCrossChainMessageAndWaitForAcceptance( - ctx, subnetAInfo, subnetBInfo, sendCrossChainMessageInput, fundedKey) - - // Relay message from SubnetA to SubnetB - network.RelayMessage(ctx, sendCrossChainMsgReceipt, subnetAInfo, subnetBInfo, true) - // Check messge delivered - delivered, err := - subnetBInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetAInfo.BlockchainID, messageID1) - Expect(err).Should(BeNil()) - Expect(delivered).Should(BeTrue()) - - // Send second message from Subnet A to Subnet B with fee amount 5 - sendCrossChainMsgReceipt, messageID2 := utils.SendCrossChainMessageAndWaitForAcceptance( - ctx, subnetAInfo, subnetBInfo, sendCrossChainMessageInput, fundedKey) - - // Relay message from SubnetA to SubnetB - network.RelayMessage(ctx, sendCrossChainMsgReceipt, subnetAInfo, subnetBInfo, true) - // Check delivered - delivered, err = - subnetBInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetAInfo.BlockchainID, messageID2) - Expect(err).Should(BeNil()) - Expect(delivered).Should(BeTrue()) - - // Relayer send specific receipts to get reward of relaying two messages - receipt, messageID := utils.SendSpecifiedReceiptsAndWaitForAcceptance( - ctx, - subnetAInfo.BlockchainID, - subnetBInfo, - []*big.Int{messageID1, messageID2}, - teleportermessenger.TeleporterFeeInfo{ - FeeTokenAddress: mockTokenAddress, - Amount: big.NewInt(0), - }, - []common.Address{}, - fundedKey, - ) - - // Relay message from Subnet B to Subnet A - network.RelayMessage(ctx, receipt, subnetBInfo, subnetAInfo, true) - // Check delivered - delivered, err = subnetAInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetBInfo.BlockchainID, messageID) - Expect(err).Should(BeNil()) - Expect(delivered).Should(BeTrue()) - - // Check the reward amount. The reward amount should be 10 - amount, err := - subnetAInfo.TeleporterMessenger.CheckRelayerRewardAmount(&bind.CallOpts{}, fundedAddress, mockTokenAddress) - Expect(err).Should(BeNil()) - Expect(amount).Should(Equal(totalAccumulatedRelayerFee)) - - // Send message from Subnet B to Subnet A to trigger the "regular" method of delivering receipts. - // The next message from B->A will contain the same receipts that were manually sent in the above steps, - // but they should not be processed again on Subnet A. - sendCrossChainMessageInput = teleportermessenger.TeleporterMessageInput{ - DestinationBlockchainID: subnetAInfo.BlockchainID, - DestinationAddress: destinationAddress, - FeeInfo: teleportermessenger.TeleporterFeeInfo{ - FeeTokenAddress: mockTokenAddress, - Amount: big.NewInt(0), - }, - RequiredGasLimit: big.NewInt(1), - AllowedRelayerAddresses: []common.Address{}, - Message: []byte{1, 2, 3, 4}, - } - - // This message will also have the same receipt as the previous message - receipt, messageID = utils.SendCrossChainMessageAndWaitForAcceptance( - ctx, subnetBInfo, subnetAInfo, sendCrossChainMessageInput, fundedKey) - - // Relay message from Subnet B to Subnet A - receipt = network.RelayMessage(ctx, receipt, subnetBInfo, subnetAInfo, true) - // Check delivered - delivered, err = subnetAInfo.TeleporterMessenger.MessageReceived(&bind.CallOpts{}, subnetBInfo.BlockchainID, messageID) - Expect(err).Should(BeNil()) - Expect(delivered).Should(BeTrue()) - // Get the Teleporter message from receive event and confirm that the receipts are delivered again - receiveEvent, err := - utils.GetEventFromLogs(receipt.Logs, subnetAInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) - Expect(err).Should(BeNil()) - Expect(receiveEvent.Message.Receipts[0].ReceivedMessageID).Should(Equal(messageID1)) - Expect(receiveEvent.Message.Receipts[1].ReceivedMessageID).Should(Equal(messageID2)) - - // Check the reward amount remains the same - amount, err = - subnetAInfo.TeleporterMessenger.CheckRelayerRewardAmount(&bind.CallOpts{}, fundedAddress, mockTokenAddress) - Expect(err).Should(BeNil()) - Expect(amount).Should(Equal(totalAccumulatedRelayerFee)) -} diff --git a/tests/testnet/main/run_testnet_flows.go b/tests/testnet/main/run_testnet_flows.go new file mode 100644 index 000000000..75b28dc73 --- /dev/null +++ b/tests/testnet/main/run_testnet_flows.go @@ -0,0 +1,51 @@ +package main + +import ( + "github.com/ethereum/go-ethereum/log" + + "github.com/ava-labs/teleporter/tests/flows" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/testnet" + "github.com/onsi/gomega" +) + +func runFlow(flowName string, flow func(interfaces.Network), network interfaces.Network) { + log.Info("Running test", "flowName", flowName) + flow(network) + log.Info("Finished running test", "flowName", flowName) +} + +func main() { + // Register a failure handler that panics + gomega.RegisterFailHandler(func(message string, callerSkip ...int) { + panic(message) + }) + + // Create the new network instances + network, err := testnet.NewTestNetwork() + if err != nil { + panic(err) + } + + // Run the Teleporter test flows. + // Note that the following flows are not able to run against testnet because they + // require the tests to be able to relay messages independently or modifying validator sets. + // - RelayerModifiesMessage + // - UnallowedRelayer + // - ValidatorSetChrun + runFlow("AddFeeAmount", flows.AddFeeAmount, network) + runFlow("BasicSendRecevie", flows.BasicSendReceive, network) + runFlow("DeliverToNonExistentContract", flows.DeliverToNonExistentContract, network) + runFlow("DeliverToWrongChain", flows.DeliverToWrongChain, network) + runFlow("InsufficientGas", flows.InsufficientGas, network) + runFlow("RelayMessageTwice", flows.RelayMessageTwice, network) + runFlow("ResubmitAlteredMessage", flows.ResubmitAlteredMessage, network) + runFlow("RetrySuccessfulExecution", flows.RetrySuccessfulExecution, network) + runFlow("SendSpecificReceipts", flows.SendSpecificReceipts, network) + log.Info("Finished Teleporter test flows") + + // Run the cross-chain application test flows. + runFlow("ExampleMessenger", flows.ExampleMessenger, network) + runFlow("ERC20BridgeMutlihop", flows.ERC20BridgeMultihop, network) + log.Info("Finished cross-chain application test flows") +} diff --git a/tests/testnet/network.go b/tests/testnet/network.go new file mode 100644 index 000000000..b7ce8cdfb --- /dev/null +++ b/tests/testnet/network.go @@ -0,0 +1,282 @@ +package testnet + +import ( + "context" + "crypto/ecdsa" + "errors" + "math/big" + "os" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/ethclient" + subnetevminterfaces "github.com/ava-labs/subnet-evm/interfaces" + teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + . "github.com/onsi/gomega" +) + +const ( + subnetAPrefix = "subnet_a" + subnetBPrefix = "subnet_b" + subnetCPrefix = "subnet_c" + teleporterContractAddress = "teleporter_contract_address" + teleporterRegistryAddressSuffix = "_teleporter_registry_address" + subnetIDSuffix = "_subnet_id" + blockchainIDSuffix = "_chain_id" + rpcURLSuffix = "_rpc_url" + wsURLSuffix = "_ws_url" + userAddress = "user_address" + userPrivateKey = "user_private_key" + + receiveCrossChainMessageEventName = "ReceiveCrossChainMessage" + receiveCrossChainMessageLookBackBlocks = 500 + + privateKeyHexLength = 64 +) + +var ( + errInvalidPrivateKeyString = errors.New("invalid private key string") +) + +var _ interfaces.Network = &testNetwork{} + +type testNetwork struct { + teleporterContractAddress common.Address + subnets []interfaces.SubnetTestInfo + fundedAddress common.Address + fundedKey *ecdsa.PrivateKey +} + +func initializeSubnetInfo( + subnetPrefix string, + teleporterContractAddress common.Address, +) (interfaces.SubnetTestInfo, error) { + subnetIDStr := os.Getenv(subnetPrefix + subnetIDSuffix) + subnetID, err := ids.FromString(subnetIDStr) + if err != nil { + return interfaces.SubnetTestInfo{}, err + } + + blockchainIDStr := os.Getenv(subnetPrefix + blockchainIDSuffix) + blockchainID, err := ids.FromString(blockchainIDStr) + if err != nil { + return interfaces.SubnetTestInfo{}, err + } + + rpcURLStr := os.Getenv(subnetPrefix + rpcURLSuffix) + rpcClient, err := ethclient.Dial(rpcURLStr) + if err != nil { + return interfaces.SubnetTestInfo{}, err + } + + wsURLStr := os.Getenv(subnetPrefix + wsURLSuffix) + wsClient, err := ethclient.Dial(wsURLStr) + if err != nil { + return interfaces.SubnetTestInfo{}, err + } + + evmChainID, err := rpcClient.ChainID(context.Background()) + if err != nil { + return interfaces.SubnetTestInfo{}, err + } + + teleporterRegistryAddress := os.Getenv(subnetPrefix + teleporterRegistryAddressSuffix) + + teleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( + teleporterContractAddress, rpcClient, + ) + if err != nil { + return interfaces.SubnetTestInfo{}, err + } + + return interfaces.SubnetTestInfo{ + SubnetID: subnetID, + BlockchainID: blockchainID, + NodeURIs: []string{}, // no specific node URIs for a testnet subnet, only RPC endpoints. + RPCClient: rpcClient, + WSClient: wsClient, + EVMChainID: evmChainID, + TeleporterRegistryAddress: common.HexToAddress(teleporterRegistryAddress), + TeleporterMessenger: teleporterMessenger, + }, nil +} + +func NewTestNetwork() (*testNetwork, error) { + teleporterContractAddressStr := os.Getenv(teleporterContractAddress) + teleporterContractAddress := common.HexToAddress(teleporterContractAddressStr) + log.Info("Set teleporter contract address", "teleporterContractAddress", teleporterContractAddressStr) + + subnetAInfo, err := initializeSubnetInfo(subnetAPrefix, teleporterContractAddress) + if err != nil { + return nil, err + } + + subnetBInfo, err := initializeSubnetInfo(subnetBPrefix, teleporterContractAddress) + if err != nil { + return nil, err + } + + subnetCInfo, err := initializeSubnetInfo(subnetCPrefix, teleporterContractAddress) + if err != nil { + return nil, err + } + log.Info("Set testnet subnet info", subnetAPrefix, subnetAInfo, subnetBPrefix, subnetBInfo, subnetCPrefix, subnetCInfo) + + fundedAddressStr := os.Getenv(userAddress) + fundedKeyStr := os.Getenv(userPrivateKey) + if len(fundedKeyStr) >= 2 && fundedKeyStr[0:2] == "0x" { + fundedKeyStr = fundedKeyStr[2:] + } + if len(fundedKeyStr) != privateKeyHexLength { + return nil, errInvalidPrivateKeyString + } + fundedKey, err := crypto.HexToECDSA(fundedKeyStr) + if err != nil { + return nil, err + } + log.Info("Set user funded address", "address", fundedAddressStr) + + return &testNetwork{ + teleporterContractAddress: teleporterContractAddress, + subnets: []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo, subnetCInfo}, + fundedAddress: common.HexToAddress(fundedAddressStr), + fundedKey: fundedKey, + }, nil +} + +func (n *testNetwork) GetSubnetsInfo() []interfaces.SubnetTestInfo { + return n.subnets +} + +func (n *testNetwork) GetTeleporterContractAddress() common.Address { + return n.teleporterContractAddress +} + +func (n *testNetwork) GetFundedAccountInfo() (common.Address, *ecdsa.PrivateKey) { + return n.fundedAddress, n.fundedKey +} + +func (n *testNetwork) IsExternalNetwork() bool { + return true +} + +func (n *testNetwork) SupportsIndependentRelaying() bool { + // The test application cannot relay its own messages on testnets + // because it can't query validators directly for their BLS signatures. + return false +} + +// For testnet messages, rely on a separately deployed relayer to relay the message. +// The implementation checks for the deliver of the given message on the destination +// within a time window of {relayWaitTime} seconds, and returns the receipt of the +// transaction that delivered the message. +func (n *testNetwork) RelayMessage(ctx context.Context, + sourceReceipt *types.Receipt, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, + expectSuccess bool) *types.Receipt { + // Set the context to expire after 20 seconds + cctx, cancel := context.WithTimeout(ctx, 20*time.Second) + defer cancel() + + sourceSubnetTeleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( + n.teleporterContractAddress, source.RPCClient, + ) + Expect(err).Should(BeNil()) + + // Get the Teleporter message ID from the receipt + sendEvent, err := utils.GetEventFromLogs( + sourceReceipt.Logs, sourceSubnetTeleporterMessenger.ParseSendCrossChainMessage, + ) + Expect(err).Should(BeNil()) + + teleporterMessageID := sendEvent.Message.MessageID + + receipt, err := n.getMessageDeliveryTransactionReceipt(cctx, source.BlockchainID, destination, teleporterMessageID) + Expect(err).Should(BeNil()) + Expect(receipt).ShouldNot(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + return receipt +} + +func (n *testNetwork) checkMessageDelivered( + sourceBlockchainID ids.ID, + destination interfaces.SubnetTestInfo, + teleporterMessageID *big.Int) (bool, error) { + destinationTeleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( + n.teleporterContractAddress, + destination.RPCClient) + if err != nil { + return false, err + } + + return destinationTeleporterMessenger.MessageReceived( + &bind.CallOpts{}, sourceBlockchainID, teleporterMessageID, + ) +} + +func (n *testNetwork) getMessageDeliveryTransactionReceipt( + ctx context.Context, + sourceBlockchainID ids.ID, + destination interfaces.SubnetTestInfo, + teleporterMessageID *big.Int) (*types.Receipt, error) { + // Wait until the message is delivered. + delivered := false + var err error + for !delivered || err != nil { + if ctx.Err() != nil { + return nil, ctx.Err() + } + delivered, err = n.checkMessageDelivered(sourceBlockchainID, destination, teleporterMessageID) + time.Sleep(time.Second) + } + + // Get the latest block height + currentBlockHeight, err := destination.RPCClient.BlockNumber(ctx) + if err != nil { + return nil, err + } + + var startBlock uint64 + if currentBlockHeight > receiveCrossChainMessageLookBackBlocks { + startBlock = currentBlockHeight - receiveCrossChainMessageLookBackBlocks + } else { + startBlock = 0 + } + + abi, err := teleportermessenger.TeleporterMessengerMetaData.GetAbi() + if err != nil { + return nil, err + } + + // Get the log event of the delivery. The log must be in the last {receiveCrossChainMessageLookBackBlocks} blocks. + logs, err := destination.RPCClient.FilterLogs(ctx, subnetevminterfaces.FilterQuery{ + FromBlock: big.NewInt(int64(startBlock)), + Addresses: []common.Address{n.teleporterContractAddress}, + Topics: [][]common.Hash{ + {abi.Events[receiveCrossChainMessageEventName].ID}, + {common.BytesToHash(sourceBlockchainID[:])}, + {common.BigToHash(teleporterMessageID)}, + }, + }) + if err != nil { + return nil, err + } + + if len(logs) == 0 { + return nil, errors.New("Failed to find ReceiveCrossChainMessage log for relayed message") + } else if len(logs) > 1 { + return nil, errors.New("Found multiple ReceiveCrossChainMessage logs for relayed message") + } + + return destination.RPCClient.TransactionReceipt(ctx, logs[0].TxHash) +} diff --git a/tests/utils/local-network-utils/local_network_setup.go b/tests/utils/local-network-utils/local_network_setup.go deleted file mode 100644 index 1e840359e..000000000 --- a/tests/utils/local-network-utils/local_network_setup.go +++ /dev/null @@ -1,526 +0,0 @@ -package localnetworkutils - -import ( - "context" - "crypto/ecdsa" - "fmt" - "math/big" - "os" - "time" - - runner_sdk "github.com/ava-labs/avalanche-network-runner/client" - "github.com/ava-labs/avalanche-network-runner/rpcpb" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/plugin/evm" - "github.com/ava-labs/subnet-evm/rpc" - "github.com/ava-labs/subnet-evm/tests/utils/runner" - erc20bridge "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ERC20Bridge/ERC20Bridge" - examplecrosschainmessenger "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ExampleMessenger/ExampleCrossChainMessenger" - blockhashpublisher "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/VerifiedBlockHash/BlockHashPublisher" - blockhashreceiver "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/VerifiedBlockHash/BlockHashReceiver" - exampleerc20 "github.com/ava-labs/teleporter/abi-bindings/go/Mocks/ExampleERC20" - teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" - teleporterregistry "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/upgrades/TeleporterRegistry" - "github.com/ava-labs/teleporter/tests/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - . "github.com/onsi/gomega" -) - -const ( - fundedKeyStr = "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027" -) - -var ( - teleporterContractAddress common.Address - subnetA, subnetB, subnetC ids.ID - subnetsInfo map[ids.ID]*utils.SubnetTestInfo = make(map[ids.ID]*utils.SubnetTestInfo) - subnetNodeNames map[ids.ID][]string = make(map[ids.ID][]string) - - globalFundedKey *ecdsa.PrivateKey - - // Internal vars only used to set up the local network - anrClient runner_sdk.Client - manager *runner.NetworkManager - warpChainConfigPath string -) - -// -// Global test state getters. Should be called within a test spec, after SetupNetwork has been called -// - -func GetSubnetsInfo() []utils.SubnetTestInfo { - return []utils.SubnetTestInfo{ - *subnetsInfo[subnetA], - *subnetsInfo[subnetB], - *subnetsInfo[subnetC], - } -} - -func GetTeleporterContractAddress() common.Address { - return teleporterContractAddress -} -func GetFundedAccountInfo() (common.Address, *ecdsa.PrivateKey) { - fundedAddress := crypto.PubkeyToAddress(globalFundedKey.PublicKey) - return fundedAddress, globalFundedKey -} - -// SetupNetwork starts the default network and adds 10 new nodes as validators with BLS keys -// registered on the P-Chain. -// Adds two disjoint sets of 5 of the new validator nodes to validate two new subnets with a -// a single Subnet-EVM blockchain. -func SetupNetwork(warpGenesisFile string) { - ctx := context.Background() - var err error - - // Name 10 new validators (which should have BLS key registered) - var subnetANodeNames []string - var subnetBNodeNames []string - var subnetCNodeNames []string - for i := 1; i <= 15; i++ { - n := fmt.Sprintf("node%d-bls", i) - if i <= 5 { - subnetANodeNames = append(subnetANodeNames, n) - } else if i <= 10 { - subnetBNodeNames = append(subnetBNodeNames, n) - } else { - subnetCNodeNames = append(subnetCNodeNames, n) - } - } - - f, err := os.CreateTemp(os.TempDir(), "config.json") - Expect(err).Should(BeNil()) - _, err = f.Write([]byte(`{"warp-api-enabled": true}`)) - Expect(err).Should(BeNil()) - warpChainConfigPath = f.Name() - - // Make sure that the warp genesis file exists - _, err = os.Stat(warpGenesisFile) - Expect(err).Should(BeNil()) - - anrConfig := runner.NewDefaultANRConfig() - log.Info("dbg", "anrConfig", anrConfig) - manager = runner.NewNetworkManager(anrConfig) - - // Construct the network using the avalanche-network-runner - _, err = manager.StartDefaultNetwork(ctx) - Expect(err).Should(BeNil()) - err = manager.SetupNetwork( - ctx, - anrConfig.AvalancheGoExecPath, - []*rpcpb.BlockchainSpec{ - { - VmName: evm.IDStr, - Genesis: warpGenesisFile, - ChainConfig: warpChainConfigPath, - SubnetSpec: &rpcpb.SubnetSpec{ - SubnetConfig: "", - Participants: subnetANodeNames, - }, - }, - { - VmName: evm.IDStr, - Genesis: warpGenesisFile, - ChainConfig: warpChainConfigPath, - SubnetSpec: &rpcpb.SubnetSpec{ - SubnetConfig: "", - Participants: subnetBNodeNames, - }, - }, - { - VmName: evm.IDStr, - Genesis: warpGenesisFile, - ChainConfig: warpChainConfigPath, - SubnetSpec: &rpcpb.SubnetSpec{ - SubnetConfig: "", - Participants: subnetCNodeNames, - }, - }, - }, - ) - Expect(err).Should(BeNil()) - - // Issue transactions to activate the proposerVM fork on the chains - globalFundedKey, err = crypto.HexToECDSA(fundedKeyStr) - Expect(err).Should(BeNil()) - SetupProposerVM(ctx, globalFundedKey, manager, 0) - SetupProposerVM(ctx, globalFundedKey, manager, 1) - SetupProposerVM(ctx, globalFundedKey, manager, 2) - - // Create the ANR client - logLevel, err := logging.ToLevel("info") - Expect(err).Should(BeNil()) - - logFactory := logging.NewFactory(logging.Config{ - DisplayLevel: logLevel, - LogLevel: logLevel, - }) - zapLog, err := logFactory.Make("main") - Expect(err).Should(BeNil()) - - anrClient, err = runner_sdk.New(runner_sdk.Config{ - Endpoint: "0.0.0.0:12352", - DialTimeout: 10 * time.Second, - }, zapLog) - Expect(err).Should(BeNil()) - - // On initial startup, we need to first set the subnet node names - // before calling setSubnetValues for the two subnets - subnetIDs := manager.GetSubnets() - Expect(len(subnetIDs)).Should(Equal(3)) - subnetA = subnetIDs[0] - subnetB = subnetIDs[1] - subnetC = subnetIDs[2] - - subnetNodeNames[subnetA] = subnetANodeNames - subnetNodeNames[subnetB] = subnetBNodeNames - subnetNodeNames[subnetC] = subnetCNodeNames - - setSubnetValues(subnetA) - setSubnetValues(subnetB) - setSubnetValues(subnetC) - - log.Info("Finished setting up e2e test subnet variables") -} - -func SetSubnetValues() { - subnetIDs := manager.GetSubnets() - Expect(len(subnetIDs)).Should(Equal(3)) - - subnetA = subnetIDs[0] - setSubnetValues(subnetA) - - subnetB = subnetIDs[1] - setSubnetValues(subnetB) - - subnetC = subnetIDs[2] - setSubnetValues(subnetC) -} - -func setSubnetValues(subnetID ids.ID) { - subnetDetails, ok := manager.GetSubnet(subnetID) - Expect(ok).Should(BeTrue()) - blockchainID := subnetDetails.BlockchainID - - // Reset the validator URIs, as they may have changed - subnetDetails.ValidatorURIs = nil - status, err := anrClient.Status(context.Background()) - Expect(err).Should(BeNil()) - nodeInfos := status.GetClusterInfo().GetNodeInfos() - - for _, nodeName := range subnetNodeNames[subnetID] { - subnetDetails.ValidatorURIs = append(subnetDetails.ValidatorURIs, nodeInfos[nodeName].Uri) - } - var chainNodeURIs []string - chainNodeURIs = append(chainNodeURIs, subnetDetails.ValidatorURIs...) - - chainWSURI := utils.HttpToWebsocketURI(chainNodeURIs[0], blockchainID.String()) - chainRPCURI := utils.HttpToRPCURI(chainNodeURIs[0], blockchainID.String()) - - if subnetsInfo[subnetID] != nil && subnetsInfo[subnetID].ChainWSClient != nil { - subnetsInfo[subnetID].ChainWSClient.Close() - } - chainWSClient, err := ethclient.Dial(chainWSURI) - Expect(err).Should(BeNil()) - if subnetsInfo[subnetID] != nil && subnetsInfo[subnetID].ChainRPCClient != nil { - subnetsInfo[subnetID].ChainRPCClient.Close() - } - chainRPCClient, err := ethclient.Dial(chainRPCURI) - Expect(err).Should(BeNil()) - chainIDInt, err := chainRPCClient.ChainID(context.Background()) - Expect(err).Should(BeNil()) - - // Set the new values in the subnetsInfo map - if subnetsInfo[subnetID] == nil { - subnetsInfo[subnetID] = &utils.SubnetTestInfo{} - } - subnetsInfo[subnetID].SubnetID = subnetID - subnetsInfo[subnetID].BlockchainID = blockchainID - subnetsInfo[subnetID].ChainNodeURIs = chainNodeURIs - subnetsInfo[subnetID].ChainWSClient = chainWSClient - subnetsInfo[subnetID].ChainRPCClient = chainRPCClient - subnetsInfo[subnetID].ChainIDInt = chainIDInt - - // TeleporterMessenger is set in DeployTeleporterContracts - // TeleporterRegistryAddress is set in DeployTeleporterRegistryContracts -} - -// DeployTeleporterContracts deploys the Teleporter contract to all subnets. -// The caller is responsible for generating the deployment transaction information -func DeployTeleporterContracts( - transactionBytes []byte, - deployerAddress common.Address, - contractAddress common.Address, - fundedKey *ecdsa.PrivateKey, -) { - log.Info("Deploying Teleporter contract to subnets") - - subnetsInfoList := GetSubnetsInfo() - - // Set the package level teleporterContractAddress - teleporterContractAddress = contractAddress - - ctx := context.Background() - - for _, subnetInfo := range subnetsInfoList { - // Fund the deployer address - { - fundAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth - fundDeployerTx := utils.CreateNativeTransferTransaction( - ctx, subnetInfo, fundedKey, deployerAddress, fundAmount, - ) - utils.SendTransactionAndWaitForAcceptance(ctx, subnetInfo, fundDeployerTx, true) - } - log.Info("Finished funding Teleporter deployer", "blockchainID", subnetInfo.BlockchainID.Hex()) - - // Deploy Teleporter contract - { - rpcClient, err := rpc.DialContext( - ctx, - utils.HttpToRPCURI(subnetInfo.ChainNodeURIs[0], subnetInfo.BlockchainID.String()), - ) - Expect(err).Should(BeNil()) - defer rpcClient.Close() - - newHeads := make(chan *types.Header, 10) - sub, err := subnetInfo.ChainWSClient.SubscribeNewHead(ctx, newHeads) - Expect(err).Should(BeNil()) - defer sub.Unsubscribe() - - err = rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(transactionBytes)) - Expect(err).Should(BeNil()) - - <-newHeads - teleporterCode, err := subnetInfo.ChainRPCClient.CodeAt(ctx, teleporterContractAddress, nil) - Expect(err).Should(BeNil()) - Expect(len(teleporterCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode - } - teleporterMessenger, err := teleportermessenger.NewTeleporterMessenger( - teleporterContractAddress, subnetInfo.ChainRPCClient, - ) - Expect(err).Should(BeNil()) - subnetsInfo[subnetInfo.SubnetID].TeleporterMessenger = teleporterMessenger - log.Info("Finished deploying Teleporter contract", "blockchainID", subnetInfo.BlockchainID.Hex()) - } - log.Info("Deployed Teleporter contracts to all subnets") -} - -func DeployTeleporterRegistryContracts( - teleporterAddress common.Address, - deployerKey *ecdsa.PrivateKey, -) { - log.Info("Deploying TeleporterRegistry contract to subnets") - ctx := context.Background() - - entries := []teleporterregistry.ProtocolRegistryEntry{ - { - Version: big.NewInt(1), - ProtocolAddress: teleporterAddress, - }, - } - - for _, subnetInfo := range GetSubnetsInfo() { - opts, err := bind.NewKeyedTransactorWithChainID(deployerKey, subnetInfo.ChainIDInt) - Expect(err).Should(BeNil()) - teleporterRegistryAddress, tx, _, err := teleporterregistry.DeployTeleporterRegistry( - opts, subnetInfo.ChainRPCClient, entries, - ) - Expect(err).Should(BeNil()) - - subnetsInfo[subnetInfo.SubnetID].TeleporterRegistryAddress = teleporterRegistryAddress - // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnetInfo.ChainRPCClient, tx) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - log.Info("Deployed TeleporterRegistry contract to subnet", subnetInfo.SubnetID.Hex(), - "Deploy address", teleporterRegistryAddress.Hex()) - } - - log.Info("Deployed TeleporterRegistry contracts to all subnets") -} - -func DeployExampleERC20( - ctx context.Context, - fundedKey *ecdsa.PrivateKey, - source utils.SubnetTestInfo, -) (common.Address, *exampleerc20.ExampleERC20) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) - Expect(err).Should(BeNil()) - - // Deploy Mock ERC20 contract - address, txn, token, err := exampleerc20.DeployExampleERC20(opts, source.ChainRPCClient) - Expect(err).Should(BeNil()) - log.Info("Deployed Mock ERC20 contract", "address", address.Hex(), "txHash", txn.Hash().Hex()) - - // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, txn) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - - return address, token -} - -func DeployExampleCrossChainMessenger( - ctx context.Context, - deployerKey *ecdsa.PrivateKey, - subnet utils.SubnetTestInfo, -) (common.Address, *examplecrosschainmessenger.ExampleCrossChainMessenger) { - opts, err := bind.NewKeyedTransactorWithChainID( - deployerKey, subnet.ChainIDInt) - Expect(err).Should(BeNil()) - address, tx, exampleMessenger, err := examplecrosschainmessenger.DeployExampleCrossChainMessenger( - opts, subnet.ChainRPCClient, subnet.TeleporterRegistryAddress, - ) - Expect(err).Should(BeNil()) - - // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnet.ChainRPCClient, tx) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - - return address, exampleMessenger -} - -func DeployERC20Bridge( - ctx context.Context, - fundedKey *ecdsa.PrivateKey, - source utils.SubnetTestInfo, -) (common.Address, *erc20bridge.ERC20Bridge) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) - Expect(err).Should(BeNil()) - address, tx, erc20Bridge, err := erc20bridge.DeployERC20Bridge( - opts, source.ChainRPCClient, source.TeleporterRegistryAddress, - ) - Expect(err).Should(BeNil()) - - // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, tx) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - - log.Info("Deployed ERC20 Bridge contract", "address", address.Hex(), "txHash", tx.Hash().Hex()) - - return address, erc20Bridge -} - -func DeployBlockHashPublisher( - ctx context.Context, - deployerKey *ecdsa.PrivateKey, - subnet utils.SubnetTestInfo, -) (common.Address, *blockhashpublisher.BlockHashPublisher) { - opts, err := bind.NewKeyedTransactorWithChainID( - deployerKey, subnet.ChainIDInt) - Expect(err).Should(BeNil()) - address, tx, publisher, err := blockhashpublisher.DeployBlockHashPublisher( - opts, subnet.ChainRPCClient, subnet.TeleporterRegistryAddress, - ) - Expect(err).Should(BeNil()) - - // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnet.ChainRPCClient, tx) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - - return address, publisher -} - -func DeployBlockHashReceiver( - ctx context.Context, - deployerKey *ecdsa.PrivateKey, - subnet utils.SubnetTestInfo, - publisherAddress common.Address, - publisherChainID [32]byte, -) (common.Address, *blockhashreceiver.BlockHashReceiver) { - opts, err := bind.NewKeyedTransactorWithChainID( - deployerKey, subnet.ChainIDInt) - Expect(err).Should(BeNil()) - address, tx, receiver, err := blockhashreceiver.DeployBlockHashReceiver( - opts, - subnet.ChainRPCClient, - subnet.TeleporterRegistryAddress, - publisherChainID, - publisherAddress, - ) - Expect(err).Should(BeNil()) - - // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, subnet.ChainRPCClient, tx) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - - return address, receiver -} - -func ExampleERC20Approve( - ctx context.Context, - token *exampleerc20.ExampleERC20, - spender common.Address, - amount *big.Int, - source utils.SubnetTestInfo, - fundedKey *ecdsa.PrivateKey, -) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) - Expect(err).Should(BeNil()) - txn, err := token.Approve(opts, spender, amount) - Expect(err).Should(BeNil()) - log.Info("Approved Mock ERC20", "spender", teleporterContractAddress.Hex(), "txHash", txn.Hash().Hex()) - - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, txn) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) -} - -func TearDownNetwork() { - log.Info("Tearing down network") - Expect(manager).ShouldNot(BeNil()) - Expect(manager.TeardownNetwork()).Should(BeNil()) - Expect(os.Remove(warpChainConfigPath)).Should(BeNil()) -} - -func RemoveSubnetValidators(ctx context.Context, subnetID ids.ID, nodeNames []string) { - _, err := anrClient.RemoveSubnetValidator(ctx, []*rpcpb.RemoveSubnetValidatorSpec{ - { - SubnetId: subnetID.String(), - NodeNames: nodeNames, - }, - }) - Expect(err).Should(BeNil()) - - // Remove the node names - currNodes := set.NewSet[string](len(subnetNodeNames[subnetID])) - currNodes.Add(subnetNodeNames[subnetID]...) - currNodes.Remove(nodeNames...) - subnetNodeNames[subnetID] = currNodes.List() - - SetSubnetValues() -} - -func AddSubnetValidators(ctx context.Context, subnetID ids.ID, nodeNames []string) { - _, err := anrClient.AddSubnetValidators(ctx, []*rpcpb.SubnetValidatorsSpec{ - { - SubnetId: subnetID.String(), - NodeNames: nodeNames, - }, - }) - Expect(err).Should(BeNil()) - - // Add the new node names - subnetNodeNames[subnetID] = append(subnetNodeNames[subnetID], nodeNames...) - - SetSubnetValues() -} - -func RestartNodes(ctx context.Context, nodeNames []string) { - for _, nodeName := range nodeNames { - _, err := anrClient.RestartNode(ctx, nodeName) - Expect(err).Should(BeNil()) - } - SetSubnetValues() -} diff --git a/tests/utils/local-network-utils/local_network_utils.go b/tests/utils/local-network-utils/local_network_utils.go deleted file mode 100644 index 2359da0ca..000000000 --- a/tests/utils/local-network-utils/local_network_utils.go +++ /dev/null @@ -1,182 +0,0 @@ -package localnetworkutils - -import ( - "context" - "crypto/ecdsa" - "time" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/interfaces" - "github.com/ava-labs/subnet-evm/params" - subnetEvmUtils "github.com/ava-labs/subnet-evm/tests/utils" - "github.com/ava-labs/subnet-evm/tests/utils/runner" - warpBackend "github.com/ava-labs/subnet-evm/warp" - "github.com/ava-labs/subnet-evm/x/warp" - "github.com/ava-labs/teleporter/tests/utils" - deploymentUtils "github.com/ava-labs/teleporter/utils/deployment-utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - - . "github.com/onsi/gomega" -) - -// Issues txs to activate the proposer VM fork on the specified subnet index in the manager -func SetupProposerVM(ctx context.Context, fundedKey *ecdsa.PrivateKey, manager *runner.NetworkManager, index int) { - subnet := manager.GetSubnets()[index] - subnetDetails, ok := manager.GetSubnet(subnet) - Expect(ok).Should(BeTrue()) - - chainID := subnetDetails.BlockchainID - uri := utils.HttpToWebsocketURI(subnetDetails.ValidatorURIs[0], chainID.String()) - - client, err := ethclient.Dial(uri) - Expect(err).Should(BeNil()) - chainIDInt, err := client.ChainID(ctx) - Expect(err).Should(BeNil()) - - err = subnetEvmUtils.IssueTxsToActivateProposerVMFork(ctx, chainIDInt, fundedKey, client) - Expect(err).Should(BeNil()) -} - -// Blocks until all validators specified in nodeURIs have reached the specified block height -func WaitForAllValidatorsToAcceptBlock(ctx context.Context, nodeURIs []string, blockchainID ids.ID, height uint64) { - cctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - for i, uri := range nodeURIs { - chainAWSURI := utils.HttpToWebsocketURI(uri, blockchainID.String()) - log.Info("Creating ethclient for blockchain", "blockchainID", blockchainID.String(), "wsURI", chainAWSURI) - client, err := ethclient.Dial(chainAWSURI) - Expect(err).Should(BeNil()) - defer client.Close() - - // Loop until each node has advanced to >= the height of the block that emitted the warp log - for { - block, err := client.BlockByNumber(cctx, nil) - Expect(err).Should(BeNil()) - if block.NumberU64() >= height { - log.Info("client accepted the block containing SendWarpMessage", "client", i, "height", block.NumberU64()) - break - } - } - } -} - -func ConstructSignedWarpMessageBytes( - ctx context.Context, - sourceReceipt *types.Receipt, - source utils.SubnetTestInfo, - destination utils.SubnetTestInfo, -) []byte { - log.Info("Fetching relevant warp logs from the newly produced block") - logs, err := source.ChainRPCClient.FilterLogs(ctx, interfaces.FilterQuery{ - BlockHash: &sourceReceipt.BlockHash, - Addresses: []common.Address{warp.Module.Address}, - }) - Expect(err).Should(BeNil()) - Expect(len(logs)).Should(Equal(1)) - - // Check for relevant warp log from subscription and ensure that it matches - // the log extracted from the last block. - txLog := logs[0] - log.Info("Parsing logData as unsigned warp message") - unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data) - Expect(err).Should(BeNil()) - - // Set local variables for the duration of the test - unsignedWarpMessageID := unsignedMsg.ID() - unsignedWarpMsg := unsignedMsg - log.Info( - "Parsed unsignedWarpMsg", - "unsignedWarpMessageID", unsignedWarpMessageID, - "unsignedWarpMessage", unsignedWarpMsg, - ) - - // Loop over each client on chain A to ensure they all have time to accept the block. - // Note: if we did not confirm this here, the next stage could be racy since it assumes every node - // has accepted the block. - WaitForAllValidatorsToAcceptBlock(ctx, source.ChainNodeURIs, source.BlockchainID, sourceReceipt.BlockNumber.Uint64()) - - // Get the aggregate signature for the Warp message - log.Info("Fetching aggregate signature from the source chain validators") - warpClient, err := warpBackend.NewClient(source.ChainNodeURIs[0], source.BlockchainID.String()) - Expect(err).Should(BeNil()) - signedWarpMessageBytes, err := warpClient.GetMessageAggregateSignature( - ctx, unsignedWarpMessageID, params.WarpQuorumDenominator, - ) - Expect(err).Should(BeNil()) - - return signedWarpMessageBytes -} - -// Constructs the aggregate signature, packs the Teleporter message, and relays to the destination -// Returns the receipt on the destination chain -func RelayMessage( - ctx context.Context, - sourceReceipt *types.Receipt, - source utils.SubnetTestInfo, - destination utils.SubnetTestInfo, - expectSuccess bool, -) *types.Receipt { - // Fetch the Teleporter message from the logs - sendEvent, err := - utils.GetEventFromLogs(sourceReceipt.Logs, source.TeleporterMessenger.ParseSendCrossChainMessage) - Expect(err).Should(BeNil()) - - signedWarpMessageBytes := ConstructSignedWarpMessageBytes(ctx, sourceReceipt, source, destination) - - // Construct the transaction to send the Warp message to the destination chain - signedTx := utils.CreateReceiveCrossChainMessageTransaction( - ctx, - signedWarpMessageBytes, - sendEvent.Message.RequiredGasLimit, - teleporterContractAddress, - globalFundedKey, - destination, - ) - - log.Info("Sending transaction to destination chain") - receipt := utils.SendTransactionAndWaitForAcceptance(ctx, destination, signedTx, expectSuccess) - - if !expectSuccess { - return nil - } - - // Check the transaction logs for the ReceiveCrossChainMessage event emitted by the Teleporter contract - receiveEvent, err := - utils.GetEventFromLogs(receipt.Logs, destination.TeleporterMessenger.ParseReceiveCrossChainMessage) - Expect(err).Should(BeNil()) - Expect(receiveEvent.OriginBlockchainID[:]).Should(Equal(source.BlockchainID[:])) - return receipt -} - -func DeployContract( - ctx context.Context, - byteCodeFileName string, - deployerPK *ecdsa.PrivateKey, - subnetInfo utils.SubnetTestInfo, - abi *abi.ABI, - constructorArgs ...interface{}, -) common.Address { - // Deploy an example ERC20 contract to be used as the source token - byteCode, err := deploymentUtils.ExtractByteCode(byteCodeFileName) - Expect(err).Should(BeNil()) - Expect(len(byteCode) > 0).Should(BeTrue()) - transactor, err := bind.NewKeyedTransactorWithChainID(deployerPK, subnetInfo.ChainIDInt) - Expect(err).Should(BeNil()) - contractAddress, tx, _, err := bind.DeployContract( - transactor, *abi, byteCode, subnetInfo.ChainRPCClient, constructorArgs..., - ) - Expect(err).Should(BeNil()) - - // Wait for transaction, then check code was deployed - utils.WaitForTransaction(ctx, tx.Hash(), subnetInfo) - code, err := subnetInfo.ChainRPCClient.CodeAt(ctx, contractAddress, nil) - Expect(err).Should(BeNil()) - Expect(len(code)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode - - return contractAddress -} diff --git a/tests/utils/utils.go b/tests/utils/utils.go index e29448474..b1683cda1 100644 --- a/tests/utils/utils.go +++ b/tests/utils/utils.go @@ -16,12 +16,16 @@ import ( avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/params" predicateutils "github.com/ava-labs/subnet-evm/predicate" "github.com/ava-labs/subnet-evm/x/warp" + erc20bridge "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ERC20Bridge/ERC20Bridge" + examplecrosschainmessenger "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/ExampleMessenger/ExampleCrossChainMessenger" + blockhashpublisher "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/VerifiedBlockHash/BlockHashPublisher" + blockhashreceiver "github.com/ava-labs/teleporter/abi-bindings/go/CrossChainApplications/VerifiedBlockHash/BlockHashReceiver" exampleerc20 "github.com/ava-labs/teleporter/abi-bindings/go/Mocks/ExampleERC20" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" + "github.com/ava-labs/teleporter/tests/interfaces" gasUtils "github.com/ava-labs/teleporter/utils/gas-utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -35,19 +39,9 @@ var ( DefaultTeleporterTransactionGasFeeCap = big.NewInt(225 * params.GWei) DefaultTeleporterTransactionGasTipCap = big.NewInt(params.GWei) DefaultTeleporterTransactionValue = common.Big0 + ExpectedExampleERC20DeployerBalance = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e10)) ) -type SubnetTestInfo struct { - SubnetID ids.ID - BlockchainID ids.ID - ChainNodeURIs []string - ChainWSClient ethclient.Client - ChainRPCClient ethclient.Client - ChainIDInt *big.Int - TeleporterRegistryAddress common.Address - TeleporterMessenger *teleportermessenger.TeleporterMessenger -} - // // Test utility functions // @@ -56,14 +50,14 @@ type SubnetTestInfo struct { // Returns the new head func SendTransactionAndWaitForAcceptance( ctx context.Context, - subnetInfo SubnetTestInfo, + subnetInfo interfaces.SubnetTestInfo, tx *types.Transaction, expectSuccess bool) *types.Receipt { - err := subnetInfo.ChainRPCClient.SendTransaction(ctx, tx) + err := subnetInfo.RPCClient.SendTransaction(ctx, tx) Expect(err).Should(BeNil()) // Wait for the transaction to be accepted - receipt, err := bind.WaitMined(ctx, subnetInfo.ChainRPCClient, tx) + receipt, err := bind.WaitMined(ctx, subnetInfo.RPCClient, tx) Expect(err).Should(BeNil()) if expectSuccess { Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -76,13 +70,13 @@ func SendTransactionAndWaitForAcceptance( func SendCrossChainMessageAndWaitForAcceptance( ctx context.Context, - source SubnetTestInfo, - destination SubnetTestInfo, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, input teleportermessenger.TeleporterMessageInput, - fundedKey *ecdsa.PrivateKey, + senderKey *ecdsa.PrivateKey, // transactor *teleportermessenger.TeleporterMessenger, ) (*types.Receipt, *big.Int) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) + opts, err := bind.NewKeyedTransactorWithChainID(senderKey, source.EVMChainID) Expect(err).Should(BeNil()) // Send a transaction to the Teleporter contract @@ -90,7 +84,7 @@ func SendCrossChainMessageAndWaitForAcceptance( Expect(err).Should(BeNil()) // Wait for the transaction to be accepted - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, txn) + receipt, err := bind.WaitMined(ctx, source.RPCClient, txn) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -108,21 +102,21 @@ func SendCrossChainMessageAndWaitForAcceptance( func SendAddFeeAmountAndWaitForAcceptance( ctx context.Context, - source SubnetTestInfo, - destination SubnetTestInfo, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, messageID *big.Int, amount *big.Int, feeContractAddress common.Address, - fundedKey *ecdsa.PrivateKey, + senderKey *ecdsa.PrivateKey, transactor *teleportermessenger.TeleporterMessenger, ) *types.Receipt { opts, err := bind.NewKeyedTransactorWithChainID( - fundedKey, source.ChainIDInt) + senderKey, source.EVMChainID) Expect(err).Should(BeNil()) txn, err := transactor.AddFeeAmount(opts, destination.BlockchainID, messageID, feeContractAddress, amount) Expect(err).Should(BeNil()) - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, txn) + receipt, err := bind.WaitMined(ctx, source.RPCClient, txn) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -142,18 +136,18 @@ func SendAddFeeAmountAndWaitForAcceptance( func RetryMessageExecutionAndWaitForAcceptance( ctx context.Context, originChainID ids.ID, - subnet SubnetTestInfo, + subnet interfaces.SubnetTestInfo, message teleportermessenger.TeleporterMessage, - fundedKey *ecdsa.PrivateKey, + senderKey *ecdsa.PrivateKey, // transactor *teleportermessenger.TeleporterMessenger, ) *types.Receipt { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnet.ChainIDInt) + opts, err := bind.NewKeyedTransactorWithChainID(senderKey, subnet.EVMChainID) Expect(err).Should(BeNil()) txn, err := subnet.TeleporterMessenger.RetryMessageExecution(opts, originChainID, message) Expect(err).Should(BeNil()) - receipt, err := bind.WaitMined(ctx, subnet.ChainRPCClient, txn) + receipt, err := bind.WaitMined(ctx, subnet.RPCClient, txn) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -162,7 +156,7 @@ func RetryMessageExecutionAndWaitForAcceptance( func RedeemRelayerRewardsAndConfirm( ctx context.Context, - subnet SubnetTestInfo, + subnet interfaces.SubnetTestInfo, feeToken *exampleerc20.ExampleERC20, feeTokenAddress common.Address, relayerKey *ecdsa.PrivateKey, @@ -176,14 +170,14 @@ func RedeemRelayerRewardsAndConfirm( Expect(err).Should(BeNil()) tx_opts, err := bind.NewKeyedTransactorWithChainID( - relayerKey, subnet.ChainIDInt, + relayerKey, subnet.EVMChainID, ) Expect(err).Should(BeNil()) transaction, err := subnet.TeleporterMessenger.RedeemRelayerRewards( tx_opts, feeTokenAddress, ) Expect(err).Should(BeNil()) - receipt, err := bind.WaitMined(ctx, subnet.ChainRPCClient, transaction) + receipt, err := bind.WaitMined(ctx, subnet.RPCClient, transaction) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -200,27 +194,32 @@ func RedeemRelayerRewardsAndConfirm( ), ) + updatedRewardAmount, err := + subnet.TeleporterMessenger.CheckRelayerRewardAmount(&bind.CallOpts{}, relayerAddress, feeTokenAddress) + Expect(err).Should(BeNil()) + Expect(updatedRewardAmount.Cmp(big.NewInt(0))).Should(Equal(0)) + return receipt } func SendSpecifiedReceiptsAndWaitForAcceptance( ctx context.Context, originChainID ids.ID, - source SubnetTestInfo, + source interfaces.SubnetTestInfo, messageIDs []*big.Int, feeInfo teleportermessenger.TeleporterFeeInfo, allowedRelayerAddresses []common.Address, - fundedKey *ecdsa.PrivateKey, + senderKey *ecdsa.PrivateKey, // transactor *teleportermessenger.TeleporterMessenger, ) (*types.Receipt, *big.Int) { - opts, err := bind.NewKeyedTransactorWithChainID(fundedKey, source.ChainIDInt) + opts, err := bind.NewKeyedTransactorWithChainID(senderKey, source.EVMChainID) Expect(err).Should(BeNil()) txn, err := source.TeleporterMessenger.SendSpecifiedReceipts( opts, originChainID, messageIDs, feeInfo, allowedRelayerAddresses) Expect(err).Should(BeNil()) - receipt, err := bind.WaitMined(ctx, source.ChainRPCClient, txn) + receipt, err := bind.WaitMined(ctx, source.RPCClient, txn) Expect(err).Should(BeNil()) Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) @@ -277,12 +276,12 @@ func GetURIHostAndPort(uri string) (string, uint32, error) { // Returns the signed transaction. func CreateSendCrossChainMessageTransaction( ctx context.Context, - source SubnetTestInfo, + source interfaces.SubnetTestInfo, input teleportermessenger.TeleporterMessageInput, - fundedKey *ecdsa.PrivateKey, + senderKey *ecdsa.PrivateKey, teleporterContractAddress common.Address, ) *types.Transaction { - fundedAddress := crypto.PubkeyToAddress(fundedKey.PublicKey) + fundedAddress := crypto.PubkeyToAddress(senderKey.PublicKey) data, err := teleportermessenger.PackSendCrossChainMessage(input) Expect(err).Should(BeNil()) @@ -290,7 +289,7 @@ func CreateSendCrossChainMessageTransaction( // Send a transaction to the Teleporter contract tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: source.ChainIDInt, + ChainID: source.EVMChainID, Nonce: nonce, To: &teleporterContractAddress, Gas: DefaultTeleporterTransactionGas, @@ -300,18 +299,18 @@ func CreateSendCrossChainMessageTransaction( Data: data, }) - return SignTransaction(tx, fundedKey, source.ChainIDInt) + return SignTransaction(tx, senderKey, source.EVMChainID) } func CreateRetryMessageExecutionTransaction( ctx context.Context, - subnetInfo SubnetTestInfo, + subnetInfo interfaces.SubnetTestInfo, originChainID ids.ID, message teleportermessenger.TeleporterMessage, - fundedKey *ecdsa.PrivateKey, + senderKey *ecdsa.PrivateKey, teleporterContractAddress common.Address, ) *types.Transaction { - fundedAddress := crypto.PubkeyToAddress(fundedKey.PublicKey) + fundedAddress := crypto.PubkeyToAddress(senderKey.PublicKey) data, err := teleportermessenger.PackRetryMessageExecution(originChainID, message) Expect(err).Should(BeNil()) @@ -324,7 +323,7 @@ func CreateRetryMessageExecutionTransaction( // Sign a transaction to the Teleporter contract tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: subnetInfo.ChainIDInt, + ChainID: subnetInfo.EVMChainID, Nonce: nonce, To: &teleporterContractAddress, Gas: gasLimit, @@ -334,7 +333,7 @@ func CreateRetryMessageExecutionTransaction( Data: data, }) - return SignTransaction(tx, fundedKey, subnetInfo.ChainIDInt) + return SignTransaction(tx, senderKey, subnetInfo.EVMChainID) } // Constructs a transaction to call receiveCrossChainMessage @@ -344,10 +343,10 @@ func CreateReceiveCrossChainMessageTransaction( warpMessageBytes []byte, requiredGasLimit *big.Int, teleporterContractAddress common.Address, - fundedKey *ecdsa.PrivateKey, - subnetInfo SubnetTestInfo, + senderKey *ecdsa.PrivateKey, + subnetInfo interfaces.SubnetTestInfo, ) *types.Transaction { - fundedAddress := crypto.PubkeyToAddress(fundedKey.PublicKey) + fundedAddress := crypto.PubkeyToAddress(senderKey.PublicKey) // Construct the transaction to send the Warp message to the destination chain log.Info("Constructing transaction for the destination chain") signedMessage, err := avalancheWarp.ParseMessage(warpMessageBytes) @@ -365,7 +364,7 @@ func CreateReceiveCrossChainMessageTransaction( gasFeeCap, gasTipCap, nonce := CalculateTxParams(ctx, subnetInfo, fundedAddress) destinationTx := predicateutils.NewPredicateTx( - subnetInfo.ChainIDInt, + subnetInfo.EVMChainID, nonce, &teleporterContractAddress, gasLimit, @@ -378,12 +377,12 @@ func CreateReceiveCrossChainMessageTransaction( signedMessage.Bytes(), ) - return SignTransaction(destinationTx, fundedKey, subnetInfo.ChainIDInt) + return SignTransaction(destinationTx, senderKey, subnetInfo.EVMChainID) } func CreateNativeTransferTransaction( ctx context.Context, - subnetInfo SubnetTestInfo, + subnetInfo interfaces.SubnetTestInfo, fromKey *ecdsa.PrivateKey, recipient common.Address, amount *big.Int, @@ -392,7 +391,7 @@ func CreateNativeTransferTransaction( gasFeeCap, gasTipCap, nonce := CalculateTxParams(ctx, subnetInfo, fromAddress) tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: subnetInfo.ChainIDInt, + ChainID: subnetInfo.EVMChainID, Nonce: nonce, To: &recipient, Gas: NativeTransferGas, @@ -401,16 +400,16 @@ func CreateNativeTransferTransaction( Value: amount, }) - return SignTransaction(tx, fromKey, subnetInfo.ChainIDInt) + return SignTransaction(tx, fromKey, subnetInfo.EVMChainID) } -func WaitForTransaction(ctx context.Context, txHash common.Hash, subnetInfo SubnetTestInfo) *types.Receipt { +func WaitForTransaction(ctx context.Context, txHash common.Hash, subnetInfo interfaces.SubnetTestInfo) *types.Receipt { cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() // Loop until we find the transaction or time out for { - receipt, err := subnetInfo.ChainRPCClient.TransactionReceipt(cctx, txHash) + receipt, err := subnetInfo.RPCClient.TransactionReceipt(cctx, txHash) if err == nil { return receipt } else { @@ -451,16 +450,16 @@ func SignTransaction(tx *types.Transaction, key *ecdsa.PrivateKey, chainID *big. // Returns the gasFeeCap, gasTipCap, and nonce the be used when constructing a transaction from fundedAddress func CalculateTxParams( ctx context.Context, - subnetInfo SubnetTestInfo, + subnetInfo interfaces.SubnetTestInfo, fundedAddress common.Address, ) (*big.Int, *big.Int, uint64) { - baseFee, err := subnetInfo.ChainRPCClient.EstimateBaseFee(ctx) + baseFee, err := subnetInfo.RPCClient.EstimateBaseFee(ctx) Expect(err).Should(BeNil()) - gasTipCap, err := subnetInfo.ChainRPCClient.SuggestGasTipCap(ctx) + gasTipCap, err := subnetInfo.RPCClient.SuggestGasTipCap(ctx) Expect(err).Should(BeNil()) - nonce, err := subnetInfo.ChainRPCClient.NonceAt(ctx, fundedAddress, nil) + nonce, err := subnetInfo.RPCClient.NonceAt(ctx, fundedAddress, nil) Expect(err).Should(BeNil()) gasFeeCap := baseFee.Mul(baseFee, big.NewInt(gasUtils.BaseFeeFactor)) @@ -468,3 +467,150 @@ func CalculateTxParams( return gasFeeCap, gasTipCap, nonce } + +func ERC20Approve( + ctx context.Context, + token *exampleerc20.ExampleERC20, + spender common.Address, + amount *big.Int, + source interfaces.SubnetTestInfo, + senderKey *ecdsa.PrivateKey, +) { + opts, err := bind.NewKeyedTransactorWithChainID(senderKey, source.EVMChainID) + Expect(err).Should(BeNil()) + txn, err := token.Approve(opts, spender, amount) + Expect(err).Should(BeNil()) + log.Info("Approved ERC20", "spender", spender.Hex(), "txHash", txn.Hash().Hex()) + + receipt, err := bind.WaitMined(ctx, source.RPCClient, txn) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) +} + +func DeployExampleERC20( + ctx context.Context, + senderKey *ecdsa.PrivateKey, + source interfaces.SubnetTestInfo, +) (common.Address, *exampleerc20.ExampleERC20) { + opts, err := bind.NewKeyedTransactorWithChainID(senderKey, source.EVMChainID) + Expect(err).Should(BeNil()) + + // Deploy Mock ERC20 contract + address, txn, token, err := exampleerc20.DeployExampleERC20(opts, source.RPCClient) + Expect(err).Should(BeNil()) + log.Info("Deployed Mock ERC20 contract", "address", address.Hex(), "txHash", txn.Hash().Hex()) + + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, source.RPCClient, txn) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + // Check that the deployer has the expected initial balance + senderAddress := crypto.PubkeyToAddress(senderKey.PublicKey) + balance, err := token.BalanceOf(&bind.CallOpts{}, senderAddress) + Expect(err).Should(BeNil()) + Expect(balance).Should(Equal(ExpectedExampleERC20DeployerBalance)) + + return address, token +} + +func DeployExampleCrossChainMessenger( + ctx context.Context, + senderKey *ecdsa.PrivateKey, + subnet interfaces.SubnetTestInfo, +) (common.Address, *examplecrosschainmessenger.ExampleCrossChainMessenger) { + opts, err := bind.NewKeyedTransactorWithChainID( + senderKey, subnet.EVMChainID) + Expect(err).Should(BeNil()) + address, tx, exampleMessenger, err := examplecrosschainmessenger.DeployExampleCrossChainMessenger( + opts, subnet.RPCClient, subnet.TeleporterRegistryAddress, + ) + Expect(err).Should(BeNil()) + + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, subnet.RPCClient, tx) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + return address, exampleMessenger +} + +func DeployERC20Bridge( + ctx context.Context, + senderKey *ecdsa.PrivateKey, + source interfaces.SubnetTestInfo, +) (common.Address, *erc20bridge.ERC20Bridge) { + opts, err := bind.NewKeyedTransactorWithChainID(senderKey, source.EVMChainID) + Expect(err).Should(BeNil()) + address, tx, erc20Bridge, err := erc20bridge.DeployERC20Bridge( + opts, source.RPCClient, source.TeleporterRegistryAddress, + ) + Expect(err).Should(BeNil()) + + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, source.RPCClient, tx) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + log.Info("Deployed ERC20 Bridge contract", "address", address.Hex(), "txHash", tx.Hash().Hex()) + + return address, erc20Bridge +} + +func DeployBlockHashPublisher( + ctx context.Context, + senderKey *ecdsa.PrivateKey, + subnet interfaces.SubnetTestInfo, +) (common.Address, *blockhashpublisher.BlockHashPublisher) { + opts, err := bind.NewKeyedTransactorWithChainID( + senderKey, subnet.EVMChainID) + Expect(err).Should(BeNil()) + address, tx, publisher, err := blockhashpublisher.DeployBlockHashPublisher( + opts, subnet.RPCClient, subnet.TeleporterRegistryAddress, + ) + Expect(err).Should(BeNil()) + + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, subnet.RPCClient, tx) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + return address, publisher +} + +func DeployBlockHashReceiver( + ctx context.Context, + senderKey *ecdsa.PrivateKey, + subnet interfaces.SubnetTestInfo, + publisherAddress common.Address, + publisherChainID [32]byte, +) (common.Address, *blockhashreceiver.BlockHashReceiver) { + opts, err := bind.NewKeyedTransactorWithChainID( + senderKey, subnet.EVMChainID) + Expect(err).Should(BeNil()) + address, tx, receiver, err := blockhashreceiver.DeployBlockHashReceiver( + opts, + subnet.RPCClient, + subnet.TeleporterRegistryAddress, + publisherChainID, + publisherAddress, + ) + Expect(err).Should(BeNil()) + + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, subnet.RPCClient, tx) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + return address, receiver +} + +func GetThreeSubnets(network interfaces.Network) ( + interfaces.SubnetTestInfo, + interfaces.SubnetTestInfo, + interfaces.SubnetTestInfo, +) { + subnets := network.GetSubnetsInfo() + Expect(len(subnets)).Should(BeNumerically(">=", 3)) + return subnets[0], subnets[1], subnets[2] +}