diff --git a/Tiltfile b/Tiltfile index 600c548118..1752c357b1 100644 --- a/Tiltfile +++ b/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: diff --git a/devnet/tx-verifier-evm.yaml b/devnet/tx-verifier-evm.yaml new file mode 100644 index 0000000000..356b81ecc6 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/devnet/tx-verifier-monitor/monitor.sh b/devnet/tx-verifier-monitor/monitor.sh deleted file mode 100755 index 9ccdc8efb1..0000000000 --- a/devnet/tx-verifier-monitor/monitor.sh +++ /dev/null @@ -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 diff --git a/devnet/tx-verifier-monitor/transfer-verifier-test.sh b/devnet/tx-verifier-monitor/transfer-verifier-test.sh deleted file mode 100755 index 18e98bef1e..0000000000 --- a/devnet/tx-verifier-monitor/transfer-verifier-test.sh +++ /dev/null @@ -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." diff --git a/devnet/tx-verifier-test.yaml b/devnet/tx-verifier-test.yaml deleted file mode 100644 index 17be61586f..0000000000 --- a/devnet/tx-verifier-test.yaml +++ /dev/null @@ -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: {} diff --git a/devnet/tx-verifier.yaml b/devnet/tx-verifier.yaml deleted file mode 100644 index 1130f76035..0000000000 --- a/devnet/tx-verifier.yaml +++ /dev/null @@ -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: {} diff --git a/devnet/tx-verifier-monitor/Dockerfile b/devnet/tx-verifier/Dockerfile similarity index 93% rename from devnet/tx-verifier-monitor/Dockerfile rename to devnet/tx-verifier/Dockerfile index 0e9be40bd3..768bfc914a 100644 --- a/devnet/tx-verifier-monitor/Dockerfile +++ b/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"] diff --git a/devnet/tx-verifier-monitor/Dockerfile.cast b/devnet/tx-verifier/Dockerfile.cast similarity index 100% rename from devnet/tx-verifier-monitor/Dockerfile.cast rename to devnet/tx-verifier/Dockerfile.cast diff --git a/devnet/tx-verifier/Dockerfile.tx-verifier-evm b/devnet/tx-verifier/Dockerfile.tx-verifier-evm new file mode 100644 index 0000000000..7dca8ac24f --- /dev/null +++ b/devnet/tx-verifier/Dockerfile.tx-verifier-evm @@ -0,0 +1,23 @@ +# We need the built guardiand image +FROM guardiand-image as guardiand + +# 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=guardiand /guardiand /bin/guardiand +COPY --from=guardiand /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 diff --git a/devnet/tx-verifier-monitor/README.md b/devnet/tx-verifier/README.md similarity index 61% rename from devnet/tx-verifier-monitor/README.md rename to devnet/tx-verifier/README.md index 7102b16531..4823771375 100644 --- a/devnet/tx-verifier-monitor/README.md +++ b/devnet/tx-verifier/README.md @@ -4,7 +4,7 @@ ### Overview -The Transfer Verifier tests involve interacting with the local ethereum devnet defined by the Tilt set-up in this repository. +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. @@ -26,39 +26,40 @@ transactions to the `anvil` instance that powers the Ethereum testnet while bein 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). + #### 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. +The error pattern is defined in `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 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-test.sh`. -The files detailed below each have a primary role and are responsible for running one of the main pieces of the test functionality: +### YAML File Description +The YAML file that runs the integration tests runs three containers: * 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 +## Further Work -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. +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. -#### devnet/tx-verifier-monitor.yaml +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. -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`. +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`. -It also defines a path to the log file that contains this string. diff --git a/devnet/tx-verifier/monitor.sh b/devnet/tx-verifier/monitor.sh new file mode 100755 index 0000000000..695041e265 --- /dev/null +++ b/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 diff --git a/devnet/tx-verifier/tx-verifier-evm-runner.sh b/devnet/tx-verifier/tx-verifier-evm-runner.sh new file mode 100644 index 0000000000..52c8b818e8 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/devnet/tx-verifier/tx-verifier-evm-tests.sh b/devnet/tx-verifier/tx-verifier-evm-tests.sh new file mode 100755 index 0000000000..cd0f2511b8 --- /dev/null +++ b/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" \ No newline at end of file diff --git a/scripts/check-docker-pin.sh b/scripts/check-docker-pin.sh index 35d43801f5..73d7b4fd8a 100755 --- a/scripts/check-docker-pin.sh +++ b/scripts/check-docker-pin.sh @@ -12,9 +12,13 @@ # - We ignore solana AS (builder|ci_tests) because it's a relative reference to another FROM call # - We ignore cosmwasm_artifacts AS artifacts because it's a local reference only, is built in tilt # - We ignore base AS (ignite-go-build|ignite-vue-build) because the base image is already pinned in wormchain/Dockerfile.proto +# - We ignore guardiand-image AS guardian because this is it's a local reference built in tilt # -git ls-files -z | grep -z "Dockerfile*" | xargs -r -0 grep -s "FROM" | egrep -v 'sha256|scratch|solana|aptos|sui|base|cosmwasm_artifacts|cli-gen|const-gen|dev AS (application|base|builder|ci_tests|tests|artifacts|ignite-go-build|ignite-vue-build|cli-export|const-export|build)' -if [ $? -eq 0 ]; then +if git ls-files -z \ + | grep -Ez "(Dockerfile)" \ + | xargs -r -0 grep -s "FROM" \ + | grep -E -v 'sha256|scratch|solana|aptos|sui|base|cosmwasm_artifacts|cli-gen|const-gen|dev|guardiand-image AS (application|base|builder|ci_tests|tests|artifacts|ignite-go-build|ignite-vue-build|cli-export|const-export|build|guardian)' +then echo "[!] Unpinned docker files" >&2 exit 1 else