Pārlūkot izejas kodu

txverifier: Increase integration test coverage and simplify Tilt set-up (#4280)

txverifier: Add integration test cases and refactor

- Add integration test case for Transfer Tokens (in addition to existing
  Transfer Tokens With Payload'
- Refactor integration test set-up. Now there is only one YAML file to
  manage instead of two. The tests have also been expanded to check for
  a variable number of error strings and include a timeout. They also
  report an error if too many error strings are found.
- The Tilt configuration now runs this test as a Job rather than a
  Deployment. This is more appropriate for a test that is meant to run
  once.

Co-authored-by: Evan Gray <56235822+evan-gray@users.noreply.github.com>

---------

Co-authored-by: pleasew8t <jason@asymmetric.re>
Co-authored-by: Evan Gray <56235822+evan-gray@users.noreply.github.com>
John Saigle 8 mēneši atpakaļ
vecāks
revīzija
d69c297363

+ 8 - 24
Tiltfile

@@ -641,15 +641,11 @@ if ci_tests:
         ],
     )
     docker_build(
-        ref = "tx-verifier-monitor", 
-        context = "./devnet/tx-verifier-monitor/",
-        dockerfile = "./devnet/tx-verifier-monitor/Dockerfile"
-    )
-    docker_build(
-        ref = "tx-verifier-test", 
-        context = "./devnet/tx-verifier-monitor/",
-        dockerfile = "./devnet/tx-verifier-monitor/Dockerfile.cast"
+        ref = "tx-verifier-evm",
+        context = "./devnet/tx-verifier/",
+        dockerfile = "./devnet/tx-verifier/Dockerfile.tx-verifier-evm"
     )
+    k8s_yaml_with_ns("devnet/tx-verifier-evm.yaml")
 
     k8s_yaml_with_ns(
         encode_yaml_stream(
@@ -660,11 +656,6 @@ if ci_tests:
                     "MAX_WORKERS", max_workers))
     )
     
-    # transfer-verifier -- daemon and log monitoring
-    k8s_yaml_with_ns("devnet/tx-verifier.yaml")
-
-    k8s_yaml_with_ns("devnet/tx-verifier-test.yaml")
-
     # separate resources to parallelize docker builds
     k8s_resource(
         "sdk-ci-tests",
@@ -696,19 +687,12 @@ if ci_tests:
         trigger_mode = trigger_mode,
         resource_deps = [], # testing/querysdk.sh handles waiting for query-server, not having deps gets the build earlier
     )
-    # launches tx-verifier binary and sets up monitoring script
+    # launches Transfer Verifier binary and sets up monitoring script
     k8s_resource(
-        "tx-verifier-with-monitor",
-        resource_deps = ["eth-devnet"],
-        labels = ["tx-verifier"],
-        trigger_mode = trigger_mode,
-    )
-    # triggers the integration tests that will be detected by the monitor
-    k8s_resource(
-        "tx-verifier-test",
-        resource_deps = ["eth-devnet", "tx-verifier-with-monitor"],
-        labels = ["tx-verifier"],
+        "tx-verifier-evm",
+        labels = ["tx-verifier-evm"],
         trigger_mode = trigger_mode,
+        resource_deps = ["eth-devnet"],
     )
 
 if terra_classic:

+ 36 - 0
devnet/tx-verifier-evm.yaml

@@ -0,0 +1,36 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: tx-verifier-evm
+spec:
+  backoffLimit: 0
+  template:
+    spec:
+      restartPolicy: Never
+      # required, as the guardian cannot run as root
+      securityContext:
+        runAsUser: 1000
+        runAsGroup: 1000
+        fsGroup: 1000
+      containers:
+        - name: tx-verifier-evm
+          image: tx-verifier-evm
+          env:
+            - name: ERROR_PATTERN
+              value: "invalid receipt: no deposits and no transfers"
+            - name: ERROR_LOG_PATH
+              value: "/tmp/error.log"
+            - name: RPC_URL
+              value: "ws://eth-devnet:8545"
+          command:
+            - /bin/bash
+            - -c
+            - "bash /tx-verifier-evm-runner.sh"
+          readinessProbe:
+            exec:
+              command:
+                - test
+                - -e
+                - "/tmp/success"
+            initialDelaySeconds: 5
+            periodSeconds: 5

+ 0 - 64
devnet/tx-verifier-monitor/README.md

@@ -1,64 +0,0 @@
-# Transfer Verifier -- Integration Tests
-
-## EVM Integration Tests
-
-### Overview
-
-The Transfer Verifier tests involve interacting with the local ethereum devnet defined by the Tilt set-up in this repository.
-
-The basic idea is as follows:
-* Interact with the local Ethereum testnet. This should already have important pieces such as the Token Bridge and Core Bridge deployed.
-* Use `cast` from the foundry tool set to simulate malicious interactions with the Token Bridge.
-* Transfer Verifier detects the malicious messages and emits errors about what went wrong.
-* The error messages are logged to a file
-* A "monitor" script is used to detect the expected error message, waiting until the file is written to
-* If the monitor script sees the expected error message in the error log, it terminates
-
-## Components
-
-### Scripts
-
-#### transfer-verifier-test.sh
-
-Contains the `cast` commands that simulate malicious interactions with the Token Bridge and Core Bridge. It is able to broadcast
-transactions to the `anvil` instance that powers the Ethereum testnet while being able to impersonate arbitrary senders.
-
-This lets us perform actions that otherwise should be impossible, like causing a Publish Message event to be emitted from the Core Bridge
-without a corresponding deposit or transfer into the Token Bridge.
-
-#### monitor.sh
-
-A bash script that monitors the error log file for a specific error pattern. It runs in an infinite loop so it will
-not exit until the error pattern is detected.
-
-The error pattern is defined in `wormhole/devnet/tx-verifier.yaml` and matches an error string in the Transfer Verifier package.
-
-Once the pattern is detected, a success message is logged to a status file. Currently this is unused but this set-up
-could be modified to detect that this script has written the success message to figure out whether the whole test completed successfully.
-
-### Pods
-
-The files detailed below each have a primary role and are responsible for running one of the main pieces of the test functionality:
-
-* The Transfer Verifier binary which monitors the state of the local Ethereum network
-* The integration test script that generates activity that the Transfer Verifier classifies as malicious
-* The monitor script which ensures that the Transfer Verifier successfully
-detected the error we expected, and signals to Tilt that the overall test has
-succeeded
-
-#### devnet/tx-verifier.yaml
-
-Runs the Transfer Verifier binary and redirects its STDERR to the error log file. This allows the output of the binary
-to be monitored by `monitor.sh`.
-
-#### devnet/tx-verifier-test.yaml
-
-Runs the `transfer-verifier-test.sh` script which simulates malicious Token Bridge activity. Defines the RPC URL used
-by that bash script, which corresponds to the `anvil` instance created in the Ethereum devnet.
-
-#### devnet/tx-verifier-monitor.yaml
-
-Defines the expected error string that should be emitted by the Transfer Verifier code assuming that it successfully recognizes
-the malicious Token Bridge activity simulated by the `cast` commands in `transfer-verifier-test.sh`.
-
-It also defines a path to the log file that contains this string.

+ 0 - 25
devnet/tx-verifier-monitor/monitor.sh

@@ -1,25 +0,0 @@
-#!/bin/sh
-
-log_file="${ERROR_LOG_PATH:-/logs/error.log}"
-error_pattern="${ERROR_PATTERN:-ERROR}"
-status_file="/logs/status"
-
-# Wait for log file to exist and be non-empty
-while [ ! -s "${log_file}" ]; do
-    echo "Waiting for ${log_file} to be created and contain data..."
-    sleep 5
-done
-
-# Initialize status
-echo "RUNNING" > "$status_file"
-echo "Monitoring file '${log_file}' for error pattern: '${error_pattern}'"
-
-# Watch for changes in the log file. If we find the error pattern that means we have
-# succeeded. (Transfer verifier should correctly detect errors.
-inotifywait -m -e modify "${log_file}" | while read -r directory events filename; do
-    if grep -q "$error_pattern" "$log_file"; then
-        echo "SUCCESS" > "$status_file"
-        echo "Found error pattern. Exiting."
-        exit 0
-    fi
-done

+ 0 - 120
devnet/tx-verifier-monitor/transfer-verifier-test.sh

@@ -1,120 +0,0 @@
-#!/usr/bin/env bash
-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 payload for token transfers. Declared on multiple lines to
-# be more legible. Data pulled from an arbitrary LogMessagePublished event
-# on etherscan. Metadata and fees commented out, leaving only the payload
-PAYLOAD="0x"
-declare -a SLOTS=(
-   # "0000000000000000000000000000000000000000000000000000000000055baf"
-   # "0000000000000000000000000000000000000000000000000000000000000000"
-   # "0000000000000000000000000000000000000000000000000000000000000080"
-   # "0000000000000000000000000000000000000000000000000000000000000001"
-   # "00000000000000000000000000000000000000000000000000000000000000ae"
-   "030000000000000000000000000000000000000000000000000000000005f5e1"
-   "000000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5"
-   "9900020000000000000000000000000000000000000000000000000000000000"
-   "000816001000000000000000000000000044eca3f6295d6d559ca1d99a5ef5a8"
-   "f72b4160f10001010200c91f01004554480044eca3f6295d6d559ca1d99a5ef5"
-   "a8f72b4160f10000000000000000000000000000000000000000000000000000"
-)
-for i in "${SLOTS[@]}"
-do
-   PAYLOAD="$PAYLOAD$i"
-done
-
-echo "DEBUG:"
-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 key: ${ANVIL_USER}"
-cast rpc \
-   anvil_impersonateAccount "${ANVIL_USER}" \
-   --rpc-url "${RPC}"
-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 User0"
-cast rpc \
-   anvil_stopImpersonatingAccount "${ANVIL_USER}" \
-   --rpc-url "${RPC}"
-
-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 
-
-# === Malicious call to transferTokensWithPayload()
-# This is the exploit scenario: the token bridge has called publishMessage() without a ERC20 Transfer or Deposit
-# being present in the same receipt.
-# This is done by impersonating the token bridge contract and sending a message directly to the core bridge.
-# Ensure that anvil is using `--auto-impersonate` or else that account impersonation is enabled in your local environment.
-# --private-key "$MNEMONIC" \
-# --max-fee 500000 \
-echo "Start impersonate token bridge" 
-cast rpc \
-   --rpc-url "${RPC}" \
-   anvil_impersonateAccount "${TOKEN_BRIDGE_CONTRACT}"
-echo "Calling publishMessage 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)" \
-   0 "${PAYLOAD}" 1
-echo ""
-cast rpc \
-   --rpc-url "${RPC}" \
-   anvil_stopImpersonatingAccount "${TOKEN_BRIDGE_CONTRACT}"
-echo "End impersonate token bridge" 
-
-# TODO add the 'multicall' scenario encoded in the forge script
-
-echo "Done Transfer Verifier integration test."
-echo "Exiting."

+ 0 - 32
devnet/tx-verifier-test.yaml

@@ -1,32 +0,0 @@
-apiVersion: batch/v1
-kind: Job
-metadata:
-  name: tx-verifier-test
-spec:
-  # Number of successful pod completions needed
-  completions: 1
-  # Number of pods to run in parallel
-  parallelism: 1
-  # Time limit after which the job is terminated (optional)
-  # activeDeadlineSeconds: 100
-  # Number of retries before marking as failed
-  backoffLimit: 4
-  template:
-    metadata:
-      labels:
-        app: tx-verifier-test
-    spec:
-      restartPolicy: Never
-      containers:
-        - name: tx-verifier-test
-          image: tx-verifier-test
-          command:
-            - /bin/bash
-            - -c
-            - "/transfer-verifier-test.sh"
-          env:
-            - name: RPC_URL
-              value: "ws://eth-devnet:8545"
-      volumes:
-      - name: log-volume
-        emptyDir: {}

+ 0 - 51
devnet/tx-verifier.yaml

@@ -1,51 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: tx-verifier-with-monitor
-spec:
-  selector:
-    matchLabels:
-      app: tx-verifier-with-monitor
-  template:
-    metadata:
-      labels:
-        app: tx-verifier-with-monitor
-    spec:
-      securityContext:
-        runAsUser: 1000
-        runAsGroup: 1000
-        fsGroup: 1000
-      containers:
-        - name: tx-verifier
-          image: guardiand-image
-          volumeMounts:
-           - name: log-volume
-             mountPath: /logs
-          command:
-            ["/bin/sh", "-c"]
-          # See `ethereum/.env.test` and related shell scripts for how these values are configured in localnet testing.
-          args:
-            - |
-              exec /guardiand \
-                           transfer-verifier \
-                           evm \
-                           --rpcUrl ws://eth-devnet:8545 \
-                           --coreContract 0xC89Ce4735882C9F0f0FE26686c53074E09B0D550 \
-                           --tokenContract 0x0290FB167208Af455bB137780163b7B7a9a10C16 \
-                           --wrappedNativeContract 0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E \
-                           --logLevel=info \
-                           2> /logs/error.log \
-        - name: tx-verifier-monitor
-          image: tx-verifier-monitor
-          volumeMounts:
-          - name: log-volume
-            mountPath: /logs
-          env:
-            - name: ERROR_PATTERN
-              # This error string comes from the transfer-verifier binary in node/
-              value: "invalid receipt: no deposits and no transfers"
-            - name: ERROR_LOG_PATH
-              value: "/logs/error.log"
-      volumes:
-      - name: log-volume
-        emptyDir: {}

+ 0 - 2
devnet/tx-verifier-monitor/Dockerfile → devnet/tx-verifier/Dockerfile

@@ -6,5 +6,3 @@ RUN apk add --no-cache inotify-tools
 
 COPY monitor.sh /monitor.sh
 RUN chmod +x /monitor.sh
-
-CMD ["/monitor.sh"]

+ 0 - 0
devnet/tx-verifier-monitor/Dockerfile.cast → devnet/tx-verifier/Dockerfile.cast


+ 23 - 0
devnet/tx-verifier/Dockerfile.tx-verifier-evm

@@ -0,0 +1,23 @@
+# We need the built guardiand image
+FROM guardiand-image AS base
+
+# These versions are pinned to match the Dockerfile in the `ethereum/`
+# directory. Otherwise, there is nothing special about them and they can be
+# updated alongside the other Dockerfile.
+FROM --platform=linux/amd64 ghcr.io/foundry-rs/foundry:nightly-55bf41564f605cae3ca4c95ac5d468b1f14447f9@sha256:8c15d322da81a6deaf827222e173f3f81c653136a3518d5eeb41250a0f2e17ea as foundry
+
+# node is required to install Foundry
+FROM node:19.6.1-slim@sha256:a1ba21bf0c92931d02a8416f0a54daad66cb36a85d2b73af9d73b044f5f57cfc
+
+# prepare cast
+COPY --from=foundry /usr/local/bin/cast /bin/cast
+
+# prepare guardiand
+COPY --from=base /guardiand /bin/guardiand
+COPY --from=base /usr/lib/libwasmvm.*.so /usr/lib/
+
+# prepare test scripts
+COPY tx-verifier-evm-tests.sh /tx-verifier-evm-tests.sh
+COPY tx-verifier-evm-runner.sh /tx-verifier-evm-runner.sh
+RUN chmod +x /tx-verifier-evm-tests.sh
+RUN chmod +x /tx-verifier-evm-runner.sh

+ 55 - 0
devnet/tx-verifier/README.md

@@ -0,0 +1,55 @@
+# Transfer Verifier -- Integration Tests
+
+## EVM Integration Tests
+
+### Overview
+
+The Transfer Verifier tests involve interacting with the local Ethereum devnet defined by the Tilt set-up in this repository.
+
+The basic idea is as follows:
+* Interact with the local Ethereum testnet. This should already have important pieces such as the Token Bridge and Core Bridge deployed.
+* Use `cast` from the foundry tool set to simulate malicious interactions with the Token Bridge.
+* Transfer Verifier detects the malicious messages and emits errors about what went wrong.
+* The error messages are logged to a file
+* A "monitor" script is used to detect the expected error message, waiting until the file is written to
+* If the monitor script sees the expected error message in the error log, it terminates
+
+## Components
+
+### Scripts
+
+#### tx-verifier-evm-tests.sh
+
+Contains the `cast` commands that simulate malicious interactions with the Token Bridge and Core Bridge. It is able to broadcast
+transactions to the `anvil` instance that powers the Ethereum testnet while being able to impersonate arbitrary senders.
+
+This lets us perform actions that otherwise should be impossible, like causing a Publish Message event to be emitted from the Core Bridge
+without a corresponding deposit or transfer into the Token Bridge.
+
+The current integration test sends exactly two messages, each one corresponding to a different Token Bridge endpoint
+(Transfer and Transfer With Payload).
+
+#### tx-verifier-evm-runner.sh
+
+Runs the guardiand binary which contains the transfer verifier tool. This tool monitors the local Ethereum network for events.
+
+A bash script that monitors the error log file for a specific error pattern. It runs in an infinite loop so it will
+not exit until the error pattern is detected.
+
+The error pattern is defined in the YAML file and matches an error string in the Transfer Verifier package.
+
+The integration test is considered successful as soon as two instances of the error pattern are detected, one for
+each message type sent by the `transfer-verifier-evm-test.sh`.
+
+## Further Work
+
+The tests cover the case where the Transfer Verifier should report when a Message Publication receipt from the 
+Token Bridge with a transfer type does not contain any deposits or transfers.
+
+However, the Transfer Verifier can do more than this. It also reports cases where the incoming funds to the Token
+Bridge within one receipt are less than the amount encoded in the payload that it sends to the Core Bridge. This
+is something like the transfer not being solvent at the resolution of one Ethereum Receipt.
+
+Adding this test would be a good improvement but requires a more complicated test pattern, perhaps combining
+multiple transactions into a single call to `cast`.
+

+ 32 - 0
devnet/tx-verifier/monitor.sh

@@ -0,0 +1,32 @@
+#!/bin/sh
+log_file="${ERROR_LOG_PATH:-/logs/error.log}"
+error_pattern="${ERROR_PATTERN:-ERROR}"
+poll_interval=5
+TARGET=2
+
+# Wait for log file to exist and be non-empty
+while [ ! -s "${log_file}" ]; do
+    echo "Waiting for ${log_file} to be created and contain data..."
+    sleep 5
+done
+
+echo "Monitoring file '${log_file}' for ${TARGET} total instance(s) of error pattern: '${error_pattern}'"
+
+# Poll until we find the target number of instances
+while true; do
+    current_count=$(grep -c "$error_pattern" "$log_file")
+    
+    echo "Found ${current_count} of ${TARGET} instances so far."
+    
+    if [ $current_count -eq $TARGET ]; then 
+        echo "SUCCESS: Found ${TARGET} instances of error pattern. Exiting."
+        exit 0
+    fi
+    
+    if [ $current_count -gt $TARGET ]; then 
+        echo "Wanted ${TARGET} instances of error pattern but got ${current_count}. This is probably a bug."
+        exit 1
+    fi
+    
+    sleep $poll_interval
+done

+ 31 - 0
devnet/tx-verifier/tx-verifier-evm-runner.sh

@@ -0,0 +1,31 @@
+#!/bin/sh
+
+log_file="${ERROR_LOG_PATH:-/logs/error.log}"
+error_pattern="${ERROR_PATTERN:-ERROR}"
+TARGET=2
+
+# start the guardian node
+guardiand \
+    transfer-verifier \
+    evm \
+    --rpcUrl ws://eth-devnet:8545 \
+    --coreContract 0xC89Ce4735882C9F0f0FE26686c53074E09B0D550 \
+    --tokenContract 0x0290FB167208Af455bB137780163b7B7a9a10C16 \
+    --wrappedNativeContract 0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E \
+    --logLevel=info \
+    2> /tmp/error.log &
+
+# start the test script
+/tx-verifier-evm-tests.sh
+
+# run the checks to see if the tests succeeded
+current_count=$(grep -c "$error_pattern" "$log_file")    
+echo "Found ${current_count} of ${TARGET} instances"
+
+# if we found the requisite number of error messages, we can exit
+if [ $current_count -ne $TARGET ]; then 
+    echo "Tests failed. Only found ${current_count} of ${TARGET} required log messages"
+    exit 1
+fi
+
+touch /tmp/success

+ 213 - 0
devnet/tx-verifier/tx-verifier-evm-tests.sh

@@ -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"