Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,15 @@ grep -hoR --include="*.bats" 'test_tags=[^ ]*' . | sed 's/.*test_tags=//' | tr '
- reth-l1
- smooth-crypto-lib
- transaction-eoa
- transaction-erc1155
- transaction-erc20
- transaction-erc721
- transaction-eth
- transaction-matic
- transaction-pol
- transaction-uniswap
- weth
- withdraw
- zkevm-batch
- zkevm-counters

Expand Down
17 changes: 17 additions & 0 deletions TESTSINVENTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,15 @@ Table of tests currently implemented or being implemented in the E2E repository.
| Transfer message | [Link](./tests/aggkit/bridge-e2e.bats#L11) | |
| Verify batches | [Link](./tests/zkevm/batch-verification.bats#L10) | |
| Verify certificate settlement | [Link](./tests/aggkit/e2e-pp.bats#L10) | |
| bridge ERC1155 from L1 to L2 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L262) | |
| bridge ERC20 from L1 to L2 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L316) | |
| bridge ERC20 from L1 to L2 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L97) | |
| bridge ERC721 from L1 to L2 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L421) | |
| bridge ERC721 from L1 to L2 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L184) | |
| bridge ETH from L1 to L2 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L206) | |
| bridge ETH from L1 to L2 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L30) | |
| bridge MATIC from L1 to L2 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L97) | |
| bridge POL from L1 to L2 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L59) | |
| bridge transaction is indexed and autoclaimed on L2 | [Link](./tests/bridge-hub-api.bats#L14) | |
| bridge transaction is indexed on L1 | [Link](./tests/bridge-hub-api.bats#L95) | |
| foo | [Link](./tests/foo.bats#L10) | |
Expand All @@ -304,6 +313,14 @@ Table of tests currently implemented or being implemented in the E2E repository.
| send multiple transactions with same nonce and verify rejection | [Link](./tests/evm-rpc/simple-validations.bats#L168) | |
| send zero priced transactions and confirm rejection | [Link](./tests/evm-rpc/simple-validations.bats#L36) | |
| trigger local balance tree underflow bridge revert | [Link](./tests/pessimistic/local-balance-tree-underflow.bats#L18) | |
| withdraw ERC1155 from L2 to L1 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L294) | |
| withdraw ERC20 from L2 to L1 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L353) | |
| withdraw ERC20 from L2 to L1 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L132) | |
| withdraw ERC721 from L2 to L1 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L465) | |
| withdraw ERC721 from L2 to L1 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L215) | |
| withdraw ETH from L2 to L1 via pos bridge | [Link](./tests/pos/bridge/pos.bats#L52) | |
| withdraw MaticWeth from L2 to L1 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L242) | |
| withdraw native tokens from L2 to L1 via plasma bridge | [Link](./tests/pos/bridge/plasma.bats#L134) | |

## Kurtosis Tests

Expand Down
174 changes: 174 additions & 0 deletions core/helpers/pos-setup.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# This function sets up environment variables for `pos` tests using a Kurtosis Polygon PoS
# environment if they are not already provided.
pos_setup() {
# Private key used to send transactions.
export PRIVATE_KEY=${PRIVATE_KEY:-"0xd40311b5a5ca5eaeb48dfba5403bde4993ece8eccf4190e98e19fcd4754260ea"}
echo "PRIVATE_KEY=${PRIVATE_KEY}"

# The name of the Kurtosis enclave (used for default values).
export ENCLAVE_NAME=${ENCLAVE_NAME:-"pos"}
echo "ENCLAVE_NAME=${ENCLAVE_NAME}"

# L1 and L2 RPC and API URLs.
if [[ -z "${L1_RPC_URL:-}" ]]; then
if l1_rpc_port=$(kurtosis port print "${ENCLAVE_NAME}" el-1-geth-lighthouse rpc 2>/dev/null); then
export L1_RPC_URL="http://${l1_rpc_port}"
elif l1_rpc_port=$(kurtosis port print "${ENCLAVE_NAME}" el-1-reth-lighthouse rpc 2>/dev/null); then
export L1_RPC_URL="http://${l1_rpc_port}"
else
echo "❌ Failed to resolve L1 RPC URL from Kurtosis (tried el-1-geth-lighthouse and el-1-reth-lighthouse)"
exit 1
fi
fi
echo "L1_RPC_URL=${L1_RPC_URL}"

export L2_RPC_URL=${L2_RPC_URL:-$(kurtosis port print "${ENCLAVE_NAME}" "l2-el-1-bor-heimdall-v2-validator" rpc)}
export L2_CL_API_URL=${L2_CL_API_URL:-$(kurtosis port print "${ENCLAVE_NAME}" "l2-cl-1-heimdall-v2-bor-validator" http)}

echo "L2_RPC_URL=${L2_RPC_URL}"
echo "L2_CL_API_URL=${L2_CL_API_URL}"

if [[ -z "${L1_GOVERNANCE_PROXY_ADDRESS:-}" ]] ||
[[ -z "${L1_ROOT_CHAIN_PROXY_ADDRESS:-}" ]] ||
[[ -z "${L1_DEPOSIT_MANAGER_PROXY_ADDRESS:-}" ]] ||
[[ -z "${L1_WITHDRAW_MANAGER_PROXY_ADDRESS:-}" ]] ||
[[ -z "${L1_ERC20_PREDICATE_ADDRESS:-}" ]] ||
[[ -z "${L1_ERC721_PREDICATE_ADDRESS:-}" ]] ||
[[ -z "${L1_STAKE_MANAGER_PROXY_ADDRESS:-}" ]] ||
[[ -z "${L1_STAKING_INFO_ADDRESS:-}" ]] ||
[[ -z "${L1_MATIC_TOKEN_ADDRESS:-}" ]] ||
[[ -z "${L1_POL_TOKEN_ADDRESS:-}" ]] ||
[[ -z "${L1_WETH_TOKEN_ADDRESS:-}" ]] ||
[[ -z "${L1_ERC20_TOKEN_ADDRESS:-}" ]] ||
[[ -z "${L1_ERC721_TOKEN_ADDRESS:-}" ]] ||
[[ -z "${L2_STATE_RECEIVER_ADDRESS:-}" ]] ||
[[ -z "${L2_WETH_TOKEN_ADDRESS:-}" ]] ||
[[ -z "${L2_ERC20_TOKEN_ADDRESS:-}" ]] ||
[[ -z "${L2_ERC721_TOKEN_ADDRESS:-}" ]]; then
plasma_bridge_addresses=$(kurtosis files inspect "${ENCLAVE_NAME}" plasma-bridge-addresses contractAddresses.json | jq)

# L1 contract addresses.
export L1_GOVERNANCE_PROXY_ADDRESS=${L1_GOVERNANCE_PROXY_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.GovernanceProxy')}
echo "L1_GOVERNANCE_PROXY_ADDRESS=${L1_GOVERNANCE_PROXY_ADDRESS}"

export L1_ROOT_CHAIN_PROXY_ADDRESS=${L1_ROOT_CHAIN_PROXY_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.RootChainProxy')}
echo "L1_ROOT_CHAIN_PROXY_ADDRESS=${L1_ROOT_CHAIN_PROXY_ADDRESS}"

export L1_DEPOSIT_MANAGER_PROXY_ADDRESS=${L1_DEPOSIT_MANAGER_PROXY_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.DepositManagerProxy')}
echo "L1_DEPOSIT_MANAGER_PROXY_ADDRESS=${L1_DEPOSIT_MANAGER_PROXY_ADDRESS}"

export L1_WITHDRAW_MANAGER_PROXY_ADDRESS=${L1_WITHDRAW_MANAGER_PROXY_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.WithdrawManagerProxy')}
echo "L1_WITHDRAW_MANAGER_PROXY_ADDRESS=${L1_WITHDRAW_MANAGER_PROXY_ADDRESS}"

export L1_ERC20_PREDICATE_ADDRESS=${L1_ERC20_PREDICATE_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.predicates.ERC20Predicate')}
echo "L1_ERC20_PREDICATE_ADDRESS=${L1_ERC20_PREDICATE_ADDRESS}"

export L1_ERC721_PREDICATE_ADDRESS=${L1_ERC721_PREDICATE_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.predicates.ERC721Predicate')}
echo "L1_ERC721_PREDICATE_ADDRESS=${L1_ERC721_PREDICATE_ADDRESS}"

export L1_STAKE_MANAGER_PROXY_ADDRESS=${L1_STAKE_MANAGER_PROXY_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.StakeManagerProxy')}
echo "L1_STAKE_MANAGER_PROXY_ADDRESS=${L1_STAKE_MANAGER_PROXY_ADDRESS}"

export L1_STAKING_INFO_ADDRESS=${L1_STAKING_INFO_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.StakingInfo')}
echo "L1_STAKING_INFO_ADDRESS=${L1_STAKING_INFO_ADDRESS}"

export L1_MATIC_TOKEN_ADDRESS=${L1_MATIC_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.tokens.MaticToken')}
echo "L1_MATIC_TOKEN_ADDRESS=${L1_MATIC_TOKEN_ADDRESS}"

export L1_POL_TOKEN_ADDRESS=${L1_POL_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.tokens.PolToken')}
echo "L1_POL_TOKEN_ADDRESS=${L1_POL_TOKEN_ADDRESS}"

export L1_ERC20_TOKEN_ADDRESS=${L1_ERC20_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.tokens.TestToken')}
echo "L1_ERC20_TOKEN_ADDRESS=${L1_ERC20_TOKEN_ADDRESS}"

export L1_ERC721_TOKEN_ADDRESS=${L1_ERC721_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.tokens.RootERC721')}
echo "L1_ERC721_TOKEN_ADDRESS=${L1_ERC721_TOKEN_ADDRESS}"

# L2 contract addresses.
export L2_STATE_RECEIVER_ADDRESS=${L2_STATE_RECEIVER_ADDRESS:-$(kurtosis files inspect "${ENCLAVE_NAME}" l2-el-genesis genesis.json | jq --raw-output '.config.bor.stateReceiverContract')}
echo "L2_STATE_RECEIVER_ADDRESS=${L2_STATE_RECEIVER_ADDRESS}"

export L2_ERC20_TOKEN_ADDRESS=${L2_ERC20_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.child.tokens.TestToken')}
echo "L2_ERC20_TOKEN_ADDRESS=${L2_ERC20_TOKEN_ADDRESS}"

export L2_ERC721_TOKEN_ADDRESS=${L2_ERC721_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.child.tokens.RootERC721')}
echo "L2_ERC721_TOKEN_ADDRESS=${L2_ERC721_TOKEN_ADDRESS}"

export L1_WETH_TOKEN_ADDRESS=${L1_WETH_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.root.tokens.MaticWeth')}
echo "L1_WETH_TOKEN_ADDRESS=${L1_WETH_TOKEN_ADDRESS}"

export L2_WETH_TOKEN_ADDRESS=${L2_WETH_TOKEN_ADDRESS:-$(echo "${plasma_bridge_addresses}" | jq --raw-output '.child.tokens.MaticWeth')}
echo "L2_WETH_TOKEN_ADDRESS=${L2_WETH_TOKEN_ADDRESS}"
fi

# pos-bridge addresses
if [[ -z "${L1_ROOT_CHAIN_MANAGER_PROXY:-}" ]] ||
[[ -z "${L1_ERC20_BRIDGE_PREDICATE_PROXY:-}" ]] ||
[[ -z "${L1_DUMMY_ERC20:-}" ]] ||
[[ -z "${L2_DUMMY_ERC1155:-}" ]]; then
pos_bridge_addresses=$(kurtosis files inspect "${ENCLAVE_NAME}" pos-bridge-addresses contractAddresses.json | jq)
export L1_ROOT_CHAIN_MANAGER_PROXY=${L1_ROOT_CHAIN_MANAGER_PROXY:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.RootChainManagerProxy')}
export L1_ERC20_BRIDGE_PREDICATE_PROXY=${L1_ERC20_BRIDGE_PREDICATE_PROXY:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.ERC20PredicateProxy')}
export L1_ERC721_BRIDGE_PREDICATE_PROXY=${L1_ERC721_BRIDGE_PREDICATE_PROXY:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.ERC721PredicateProxy')}
export L1_ERC1155_BRIDGE_PREDICATE_PROXY=${L1_ERC1155_BRIDGE_PREDICATE_PROXY:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.ERC1155PredicateProxy')}
export L1_ETHER_BRIDGE_PREDICATE_PROXY=${L1_ETHER_BRIDGE_PREDICATE_PROXY:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.EtherPredicateProxy')}
export L1_DUMMY_ERC20=${L1_DUMMY_ERC20:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.DummyERC20')}
export L2_DUMMY_ERC20=${L2_DUMMY_ERC20:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.child.posBridge.DummyERC20')}
export L1_DUMMY_ERC721=${L1_DUMMY_ERC721:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.DummyERC721')}
export L2_DUMMY_ERC721=${L2_DUMMY_ERC721:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.child.posBridge.DummyERC721')}
export L1_DUMMY_ERC1155=${L1_DUMMY_ERC1155:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.root.posBridge.DummyERC1155')}
export L2_DUMMY_ERC1155=${L2_DUMMY_ERC1155:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.child.posBridge.DummyERC1155')}
export L2_MATIC_WETH=${L2_MATIC_WETH:-$(echo "${pos_bridge_addresses}" | jq --raw-output '.child.posBridge.MaticWETH')}
echo "L1_ROOT_CHAIN_MANAGER_PROXY=${L1_ROOT_CHAIN_MANAGER_PROXY}"
echo "L2_MATIC_WETH=${L2_MATIC_WETH}"
fi
}

# Create and fund an ephemeral wallet. Sets ephemeral_private_key and
# ephemeral_address. Calls `skip` if the chain is not processing transactions
# (e.g. stalled in mixed-version networks).
#
# Usage: _fund_ephemeral [amount] (default: 1ether)
_fund_ephemeral() {
local amount="${1:-1ether}"
local wallet_json
wallet_json=$(cast wallet new --json | jq '.[0]')
# shellcheck disable=SC2034 # intentional global: used by calling test scripts
ephemeral_private_key=$(echo "$wallet_json" | jq -r '.private_key')
ephemeral_address=$(echo "$wallet_json" | jq -r '.address')
echo "ephemeral_address: $ephemeral_address" >&3

local _err
if ! _err=$(cast send --rpc-url "$L2_RPC_URL" --private-key "$PRIVATE_KEY" \
--legacy --gas-limit 21000 --value "$amount" "$ephemeral_address" 2>&1 >/dev/null); then
case "$_err" in
*"replacement transaction underpriced"*|*"not confirmed within"*|*"nonce too low"*)
skip "Chain stalled — cannot fund ephemeral wallet"
;;
*)
echo "Fund ephemeral failed: $_err" >&2
return 1
;;
esac
fi
}

# Wrapper around `cast send` that skips the test on chain-stall errors.
# Use inside @test functions where a `cast send` is needed.
#
# Usage: _send_or_skip [cast send args...]
_send_or_skip() {
local _err
if ! _err=$(cast send "$@" 2>&1); then
case "$_err" in
*"replacement transaction underpriced"*|*"not confirmed within"*|*"nonce too low"*)
skip "Chain stalled — transaction cannot be submitted"
;;
*)
echo "$_err" >&2
return 1
;;
esac
fi
echo "$_err"
}
85 changes: 85 additions & 0 deletions core/helpers/scripts/pos-bridge.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/bash
# Shared helpers for Polygon PoS bridge tests (both plasma and pos bridge).
# Sourced from tests/pos/bridge/*.bats via `load` in setup().
#
# Assumes pos_setup() has already been called (defines L1/L2 env vars, timeout_seconds,
# interval_seconds).

# Variables set by test environment setup functions - disable shellcheck warnings
# shellcheck disable=SC2154
declare timeout_seconds interval_seconds

# Commands that read the current state-sync counter on each side. Re-evaluated via
# `eval` by the eventually helpers so ${L2_CL_API_URL} / ${L2_RPC_URL} are picked up
# at the time of the call.
heimdall_state_sync_count_cmd='curl "${L2_CL_API_URL}/clerk/event-records/count" | jq -r ".count"'
bor_state_sync_count_cmd='cast call --gas-limit 15000000 --rpc-url "${L2_RPC_URL}" "${L2_STATE_RECEIVER_ADDRESS}" "lastStateId()(uint)"'
checkpoint_count_cmd='curl -s "${L2_CL_API_URL}/checkpoints/latest" | jq --raw-output ".checkpoint.id"'

# Wait for Heimdall to observe at least one new state sync since `state_sync_count`.
wait_for_heimdall_state_sync() {
local state_sync_count="$1"
echo "Monitoring state syncs on Heimdall..."
assert_command_eventually_greater_or_equal "${heimdall_state_sync_count_cmd}" $((state_sync_count + 1)) "${timeout_seconds}" "${interval_seconds}"
}

# Wait for Bor to observe at least one new state sync since `state_sync_count`.
wait_for_bor_state_sync() {
local state_sync_count="$1"
echo "Monitoring state syncs on Bor..."
assert_command_eventually_greater_or_equal "${bor_state_sync_count_cmd}" $((state_sync_count + 1)) "${timeout_seconds}" "${interval_seconds}"
}

# Convenience: snapshot both counters, run $1 (a command), then wait for both counters
# to increment. Used by tests that trigger an L1 deposit and need the full state-sync
# round-trip to complete before asserting on L2 state.
wait_for_state_sync_after_deposit() {
local initial_hm="$1"
local initial_bor="$2"
wait_for_heimdall_state_sync "${initial_hm}"
wait_for_bor_state_sync "${initial_bor}"
}

# Read the latest checkpoint id (0 if none yet).
latest_checkpoint_id() {
local id
id=$(eval "${checkpoint_count_cmd}")
[[ "${id}" == "null" ]] && id=0
echo "${id}"
}

# Block until a checkpoint with id > ${initial_id} exists on Heimdall.
wait_for_new_checkpoint() {
local initial_id="$1"
echo "Waiting for a new checkpoint on L1..."
assert_command_eventually_greater_or_equal "${checkpoint_count_cmd}" $((initial_id + 1)) "${timeout_seconds}" "${interval_seconds}"
}

# Generate the ABI-encoded exit payload for a burn tx on L2 via polycli. Both the plasma
# bridge's ERC20PredicateBurnOnly.startExitWithBurntTokens(bytes) and the pos bridge's
# RootChainManager.exit(bytes) consume the same format.
#
# Usage: generate_pos_exit_payload <tx_hash> [log_index=0] [timeout=${timeout_seconds}]
generate_pos_exit_payload() {
local tx_hash="$1"
local log_index="${2:-0}"
local timeout="${3:-${timeout_seconds}}"
local deadline=$((SECONDS + timeout))
local payload=""
while [[ $SECONDS -lt $deadline ]]; do
echo "Trying to generate exit payload for tx ${tx_hash} (log-index=${log_index})..." >&2
if payload=$(polycli pos exit-proof \
--l1-rpc-url "${L1_RPC_URL}" \
--l2-rpc-url "${L2_RPC_URL}" \
--root-chain-address "${L1_ROOT_CHAIN_PROXY_ADDRESS}" \
--tx-hash "${tx_hash}" \
--log-index "${log_index}"); then
echo "${payload}"
return 0
fi
echo "Checkpoint not yet indexed, retrying in ${interval_seconds}s..." >&2
sleep "${interval_seconds}"
done
echo "Error: failed to generate exit payload for tx ${tx_hash} within ${timeout} seconds." >&2
return 1
}
Loading
Loading