|
|
@@ -0,0 +1,213 @@
|
|
|
+#!/usr/bin/env bash
|
|
|
+# Overview
|
|
|
+# This script simulates unusual, dangerous messages being emitted by the Core Bridge.
|
|
|
+# Its purpose is to be used as an integration test for the Transfer Verifier. The
|
|
|
+# Transfer Verifier can be set up as standalone binary to monitor the core bridge.
|
|
|
+# Once this is done, this script can be used to simulate dangerous scenarios. The
|
|
|
+# Transfer Verifier should pick up on this behaviour and log errors accordingly.
|
|
|
+#
|
|
|
+# Strategy
|
|
|
+# The code below sets up several contract and wallet addresses that match
|
|
|
+# the state of the devnet created by Tilt. Using the anvil and cast tools
|
|
|
+# from foundry, it impersonates the Token Bridge, setting its address
|
|
|
+# as the sender for messages to the core bridge. It sends messages that
|
|
|
+# contain token transfer data. The Core Bridge emits a Message Publication
|
|
|
+# as a result. However, no actual funds have been sent from a user into the
|
|
|
+# Token Bridge. This is the danger that Transfer Verifier should detect.
|
|
|
+#
|
|
|
+# Coverage
|
|
|
+# The code currently sends both a Transfer and Transfer With Payload type.
|
|
|
+# As a result, exactly 2 violations should be detected by the Transfer Verifier.
|
|
|
+# It should report an error that says that a message was sent without any
|
|
|
+# transfers or deposits in its receipt. (Compare this with
|
|
|
+# devnet/tx-verifier-monitor/monitor.sh)
|
|
|
+
|
|
|
+set -euo pipefail
|
|
|
+
|
|
|
+RPC="${RPC_URL:-ws://eth-devnet:8545}"
|
|
|
+
|
|
|
+# mainnet values
|
|
|
+# export CORE_CONTRACT="0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B"
|
|
|
+# export TOKEN_BRIDGE_CONTRACT="0x3ee18B2214AFF97000D974cf647E7C347E8fa585"
|
|
|
+
|
|
|
+# TODO these could be CLI params from the sh/devnet script
|
|
|
+CORE_BRIDGE_CONTRACT=0xC89Ce4735882C9F0f0FE26686c53074E09B0D550
|
|
|
+TOKEN_BRIDGE_CONTRACT=0x0290FB167208Af455bB137780163b7B7a9a10C16
|
|
|
+
|
|
|
+MNEMONIC=0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d
|
|
|
+
|
|
|
+ERC20_ADDR="0x47bdB2D7d6528C760b6f228b3B8F9F650169a10f" # Test token A
|
|
|
+
|
|
|
+VALUE="1000" # Wei value sent as msg.value
|
|
|
+TRANSFER_AMOUNT="10"
|
|
|
+
|
|
|
+# This account is generated by anvil and can be confirmed by running `anvil --accounts=13`.
|
|
|
+# The accounts at other indices are used by other tests in the test suite, so
|
|
|
+# account[13] is used here to help encapsulate the tests.
|
|
|
+ANVIL_USER="0x64E078A8Aa15A41B85890265648e965De686bAE6"
|
|
|
+ETH_WHALE="${ANVIL_USER}"
|
|
|
+FROM="${ETH_WHALE}"
|
|
|
+# Anvil user1 normalized to Wormhole size. (The value itself it unchecked but must have this format.)
|
|
|
+RECIPIENT="0x00000000000000000000000064E078A8Aa15A41B85890265648e965De686bAE6"
|
|
|
+NONCE="234" # arbitrary
|
|
|
+
|
|
|
+# Build the payloads for token transfers. The payloads are built using
|
|
|
+# arrays of strings and are concatenated together in a loop. Also, the
|
|
|
+# data is declared on multiple lines in 32 bytes chunks. This isn't
|
|
|
+# necessary but it makes the data a little easier to grok and work with.
|
|
|
+#
|
|
|
+# The data is pulled from an arbitrary LogMessagePublished event on
|
|
|
+# etherscan. Metadata and fees are commented out, leaving only the payload.
|
|
|
+# Note that the first non-commented byte corresponds to the payload type.
|
|
|
+# (https://wormhole.com/docs/learn/infrastructure/vaas/#payload-types)
|
|
|
+
|
|
|
+declare -a TRANSFER_SLOTS=(
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000077009"
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000067be1656"
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000000080"
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000000001"
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000000085"
|
|
|
+ "01000000000000000000000000000000000000000000000000000006c0260fe4"
|
|
|
+ "5f0000000000000000000000006b0b3a982b4634ac68dd83a4dbf02311ce3241"
|
|
|
+ "810002a5f3c5bf0f76bb899300b7ec6345ed3740f22fd4bb79501bc6204938fc"
|
|
|
+ "3a6c780001000000000000000000000000000000000000000000000000000000"
|
|
|
+ "0000000000000000000000000000000000000000000000000000000000000000"
|
|
|
+)
|
|
|
+
|
|
|
+# Note that the token address of `ERC20_ADDR` appears within the payload
|
|
|
+# but is not aligned with the slot size, i.e. the last byte of
|
|
|
+# the address is found on the following line. This is not ideal to work
|
|
|
+# with but it matches how the core bridge actually works.
|
|
|
+declare -a TRANSFER_WITH_PAYLOAD_SLOTS=(
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000055baf"
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000000000"
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000000080"
|
|
|
+ # "0000000000000000000000000000000000000000000000000000000000000001"
|
|
|
+ # "00000000000000000000000000000000000000000000000000000000000000ae"
|
|
|
+ "030000000000000000000000000000000000000000000000000000000005f5e1"
|
|
|
+ "000000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5"
|
|
|
+ "9900020000000000000000000000000000000000000000000000000000000000"
|
|
|
+ "000816001000000000000000000000000044eca3f6295d6d559ca1d99a5ef5a8"
|
|
|
+ "f72b4160f10001010200c91f01004554480044eca3f6295d6d559ca1d99a5ef5"
|
|
|
+ "a8f72b4160f10000000000000000000000000000000000000000000000000000"
|
|
|
+)
|
|
|
+
|
|
|
+# Build payloads by joining the arrays of hex strings.
|
|
|
+TRANSFER_DATA="0x"
|
|
|
+TRANSFER_WITH_PAYLOAD_DATA="0x"
|
|
|
+
|
|
|
+for i in "${TRANSFER_SLOTS[@]}"
|
|
|
+do
|
|
|
+ TRANSFER_DATA="$TRANSFER_DATA$i"
|
|
|
+done
|
|
|
+
|
|
|
+for i in "${TRANSFER_WITH_PAYLOAD_SLOTS[@]}"
|
|
|
+do
|
|
|
+ TRANSFER_WITH_PAYLOAD_DATA="$TRANSFER_WITH_PAYLOAD_DATA$i"
|
|
|
+done
|
|
|
+
|
|
|
+# Header information for beginning the test.
|
|
|
+echo "Beginning test with the following configuration"
|
|
|
+echo "- RPC=${RPC}"
|
|
|
+echo "- CORE_BRIDGE_CONTRACT=${CORE_BRIDGE_CONTRACT}"
|
|
|
+echo "- TOKEN_BRIDGE_CONTRACT=${TOKEN_BRIDGE_CONTRACT}"
|
|
|
+echo "- MNEMONIC=${MNEMONIC}"
|
|
|
+echo "- FROM=${FROM}"
|
|
|
+echo "- VALUE=${VALUE}"
|
|
|
+echo "- RECIPIENT=${RECIPIENT}"
|
|
|
+echo
|
|
|
+
|
|
|
+# Fund the token bridge from the user.
|
|
|
+echo "Start impersonating Anvil user: ${ANVIL_USER}"
|
|
|
+cast rpc \
|
|
|
+ anvil_impersonateAccount "${ANVIL_USER}" \
|
|
|
+ --rpc-url "${RPC}"
|
|
|
+
|
|
|
+# Set-up function to make sure that the token bridge in the devnet is solvent.
|
|
|
+echo "Funding token bridge using the user's balance"
|
|
|
+cast send --unlocked \
|
|
|
+ --rpc-url "${RPC}" \
|
|
|
+ --from $ANVIL_USER \
|
|
|
+ --value 100000000000000 \
|
|
|
+ ${TOKEN_BRIDGE_CONTRACT}
|
|
|
+echo ""
|
|
|
+echo "End impersonating ${ANVIL_USER}"
|
|
|
+cast rpc \
|
|
|
+ anvil_stopImpersonatingAccount "${ANVIL_USER}" \
|
|
|
+ --rpc-url "${RPC}"
|
|
|
+
|
|
|
+# Start the test, printing the conditions before any Token Bridge
|
|
|
+# activity (except the initial funding).
|
|
|
+BALANCE_CORE=$(cast balance --rpc-url "${RPC}" $CORE_BRIDGE_CONTRACT)
|
|
|
+BALANCE_TOKEN=$(cast balance --rpc-url "${RPC}" $TOKEN_BRIDGE_CONTRACT)
|
|
|
+BALANCE_USER=$(cast balance --rpc-url "${RPC}" $ANVIL_USER)
|
|
|
+echo "BALANCES:"
|
|
|
+echo "- CORE_BRIDGE_CONTRACT=${BALANCE_CORE}"
|
|
|
+echo "- TOKEN_BRIDGE_CONTRACT=${BALANCE_TOKEN}"
|
|
|
+echo "- ANVIL_USER=${BALANCE_USER}"
|
|
|
+echo
|
|
|
+
|
|
|
+# Simulate the Token Bridge so that it's possible to communicate with the core
|
|
|
+# bridge in arbitrary ways. Normally, the only path to do this would involve
|
|
|
+# doing a WETH Deposit or ERC20 transfer in the same transaction. This is
|
|
|
+# exactly the invariant that we are trying to test with Transfer Verifier. If
|
|
|
+# the Token Bridge can cause a Message Publication from the Core Bridge without
|
|
|
+# any funds being sent into in to the Token Bridge first, there's a serious
|
|
|
+# error.
|
|
|
+# Ensure that anvil is using `--auto-impersonate` or else that account
|
|
|
+# impersonation is enabled in your local environment. For Tilt, this should
|
|
|
+# be handled already by the eth-devnet pod.
|
|
|
+echo "Start impersonating the Token Bridge"
|
|
|
+cast rpc \
|
|
|
+ --rpc-url "${RPC}" \
|
|
|
+ anvil_impersonateAccount "${TOKEN_BRIDGE_CONTRACT}" &> /dev/null
|
|
|
+
|
|
|
+# === Test Scenario 1: Malicious call to transferTokens()
|
|
|
+NONCE=0
|
|
|
+echo "Calling publishMessage() with transferTokens() payload as ${TOKEN_BRIDGE_CONTRACT}"
|
|
|
+cast send --unlocked \
|
|
|
+ --rpc-url "${RPC}" \
|
|
|
+ --json \
|
|
|
+ --gas-limit 10000000 \
|
|
|
+ --priority-gas-price 1 \
|
|
|
+ --from "${TOKEN_BRIDGE_CONTRACT}" \
|
|
|
+ --value "0" \
|
|
|
+ "${CORE_BRIDGE_CONTRACT}" \
|
|
|
+ "publishMessage(uint32,bytes,uint8)" \
|
|
|
+ $NONCE "${TRANSFER_DATA}" 1
|
|
|
+echo ""
|
|
|
+NONCE=$(($NONCE + 1))
|
|
|
+
|
|
|
+# === Test Scenario 2: Malicious call to transferTokensWithPayload()
|
|
|
+echo "Calling publishMessage() with transferTokensWithPayload() payload as ${TOKEN_BRIDGE_CONTRACT}"
|
|
|
+cast send --unlocked \
|
|
|
+ --rpc-url "${RPC}" \
|
|
|
+ --json \
|
|
|
+ --gas-limit 10000000 \
|
|
|
+ --priority-gas-price 1 \
|
|
|
+ --from "${TOKEN_BRIDGE_CONTRACT}" \
|
|
|
+ --value "0" \
|
|
|
+ "${CORE_BRIDGE_CONTRACT}" \
|
|
|
+ "publishMessage(uint32,bytes,uint8)" \
|
|
|
+ 1 "${TRANSFER_WITH_PAYLOAD_DATA}" 1
|
|
|
+echo ""
|
|
|
+
|
|
|
+# Cleanup.
|
|
|
+cast rpc \
|
|
|
+ --rpc-url "${RPC}" \
|
|
|
+ anvil_stopImpersonatingAccount "${TOKEN_BRIDGE_CONTRACT}" &> /dev/null
|
|
|
+echo "End impersonating the Token Bridge"
|
|
|
+
|
|
|
+# Print balances after.
|
|
|
+BALANCE_CORE=$(cast balance --rpc-url "${RPC}" $CORE_BRIDGE_CONTRACT)
|
|
|
+BALANCE_TOKEN=$(cast balance --rpc-url "${RPC}" $TOKEN_BRIDGE_CONTRACT)
|
|
|
+BALANCE_USER=$(cast balance --rpc-url "${RPC}" $ANVIL_USER)
|
|
|
+echo "BALANCES:"
|
|
|
+echo "- CORE_BRIDGE_CONTRACT=${BALANCE_CORE}"
|
|
|
+echo "- TOKEN_BRIDGE_CONTRACT=${BALANCE_TOKEN}"
|
|
|
+echo "- ANVIL_USER=${BALANCE_USER}"
|
|
|
+echo
|
|
|
+
|
|
|
+# TODO add the 'multicall' scenario encoded in the forge script
|
|
|
+
|
|
|
+echo "Done Transfer Verifier integration test. Exiting"
|