Skip to content

Commit a567951

Browse files
committed
test: new swaps api
1 parent 1bdb8a3 commit a567951

7 files changed

Lines changed: 1614 additions & 0 deletions

.github/workflows/test.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,11 @@ jobs:
5353
LINEA_RPC_URL: ${{ secrets.LINEA_RPC_URL }}
5454
ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }}
5555
RPC_API_KEY: ${{ secrets.RPC_API_KEY }}
56+
57+
# The new-API Linea fork suite needs a post-London executor spec (live Linea aggregator contracts use
58+
# PUSH0 etc.), so it self-skips under the default profile above and runs for real here.
59+
- name: Run new-API Linea fork tests (linea-fork profile)
60+
run: forge test --match-contract DelegationMetaSwapAdapterNewApiForkTest -vvv
61+
env:
62+
FOUNDRY_PROFILE: linea-fork
63+
LINEA_RPC_URL: ${{ secrets.LINEA_RPC_URL }}

foundry.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ sparse_mode = true
1313
evm_version = "london"
1414
optimizer = true
1515

16+
# Profile for Linea fork tests (e.g. DelegationMetaSwapAdapterNewApiForkTest). Linea mainnet now runs post-London
17+
# fork rules and the live aggregator contracts (1inch v6 executors, KyberSwap, Mayan, Relay) use post-London opcodes
18+
# such as PUSH0, which revert with EvmError: NotActivated under the default "london" executor spec. solc 0.8.23 caps
19+
# the compile-time EVM target automatically, so only the runtime spec changes.
20+
# Usage: FOUNDRY_PROFILE=linea-fork forge test --match-contract DelegationMetaSwapAdapterNewApiForkTest
21+
[profile.linea-fork]
22+
evm_version = "prague"
23+
1624
[fmt]
1725
bracket_spacing = true
1826
int_types = "long"
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Fetches fresh SIGNED quotes from the new swap API and regenerates the new-API test fixtures:
4+
# 1. GETs https://bridge.dev-api.cx.metamask.io/getQuote (signQuotes=true) for the 3 Linea pairs:
5+
# - ERC20 -> ERC20 (USDC -> WETH)
6+
# - NATIVE -> ERC20 (ETH -> USDC)
7+
# - ERC20 -> NATIVE (USDC -> ETH)
8+
# 2. Rebuilds test/helpers/data/new_api_fixtures.json (same shape consumed by the generator), capturing the
9+
# CURRENT Linea block number via `cast block-number` as meta.fetchedAtLineaBlock.
10+
# 3. Runs scripts/generate_new_api_fixtures.py, which regenerates
11+
# test/helpers/DelegationMetaSwapAdapterNewApiFixtures.t.sol.
12+
#
13+
# IMPORTANT: the fork block pin used by DelegationMetaSwapAdapterNewApiForkTest is the generated constant
14+
# NEW_API_FORK_BLOCK, which comes from meta.fetchedAtLineaBlock. Because this script captures the block number
15+
# at fetch time, re-running it updates the fork pin ALONGSIDE the fresh quotes automatically — never bump the
16+
# fork block without re-fetching quotes (and vice versa), or the quoted amounts will not match on-chain
17+
# liquidity and the fork tests will produce false failures.
18+
#
19+
# Requirements: curl, jq, cast (foundry), python3.
20+
#
21+
# Usage:
22+
# ./scripts/fetch_new_api_signed_quotes.sh
23+
# LINEA_RPC_URL=<url> ./scripts/fetch_new_api_signed_quotes.sh
24+
25+
set -euo pipefail
26+
27+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
28+
OUT_JSON="${REPO_ROOT}/test/helpers/data/new_api_fixtures.json"
29+
30+
API_BASE="https://bridge.dev-api.cx.metamask.io/getQuote"
31+
CHAIN_ID=59144
32+
# Arbitrary EOA used only to request quotes. The MetaSwap architecture pays the swap output to msg.sender
33+
# (the adapter), so the quoted walletAddress should not affect on-chain execution. If an aggregator ever
34+
# embeds it as a recipient in trade.data, the fork tests will surface it as a real compatibility finding.
35+
WALLET_ADDRESS="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
36+
SLIPPAGE=2
37+
USDC="0x176211869cA2b568f2A7D4EE941E073a821EE1ff"
38+
WETH="0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f"
39+
NATIVE="0x0000000000000000000000000000000000000000"
40+
USDC_AMOUNT="1000000000" # 1,000 USDC (6 decimals)
41+
NATIVE_AMOUNT="1000000000000000000" # 1 ETH
42+
43+
LINEA_RPC_URL="${LINEA_RPC_URL:-https://rpc.linea.build}"
44+
QUERY_PARAMS="slippage=${SLIPPAGE}&insufficientBal=true&signQuotes=true"
45+
46+
for tool in curl jq cast python3; do
47+
command -v "${tool}" > /dev/null || { echo "error: ${tool} is required" >&2; exit 1; }
48+
done
49+
50+
TMP_DIR="$(mktemp -d)"
51+
trap 'rm -rf "${TMP_DIR}"' EXIT
52+
53+
fetch_pair() {
54+
# fetch_pair <pairLabel> <srcToken> <destToken> <srcTokenAmount>
55+
local pair="$1" src="$2" dest="$3" amount="$4"
56+
local url="${API_BASE}?walletAddress=${WALLET_ADDRESS}&srcChainId=${CHAIN_ID}&destChainId=${CHAIN_ID}"
57+
url+="&srcTokenAddress=${src}&destTokenAddress=${dest}&srcTokenAmount=${amount}&${QUERY_PARAMS}"
58+
59+
echo "Fetching ${pair} quotes..." >&2
60+
local raw="${TMP_DIR}/${pair}_raw.json"
61+
curl -sSf --max-time 120 "${url}" > "${raw}"
62+
63+
# SILENT-FILTER GUARD: the jq filter below keeps only signed, MetaSwap-format quotes (selector 0x5f575529).
64+
# Any quote the API returns in a different shape is a quote the CURRENT adapter cannot execute — that is a
65+
# potential COMPATIBILITY FINDING, not noise, so it must be surfaced loudly rather than silently excluded.
66+
local total_count filtered_count
67+
total_count="$(jq 'length' "${raw}")"
68+
filtered_count="$(jq '[ .[]
69+
| select(.signature != null and .sigExpiration != null)
70+
| select(.trade.data | ascii_downcase | startswith("0x5f575529"))
71+
] | length' "${raw}")"
72+
echo " ${pair}: ${filtered_count}/${total_count} quotes are signed MetaSwap-format quotes" >&2
73+
if [ "${filtered_count}" -ne "${total_count}" ]; then
74+
echo "WARNING: ${pair}: DROPPED $((total_count - filtered_count)) quote(s) that are unsigned or not" >&2
75+
echo " IMetaSwap.swap calldata. The CURRENT DelegationMetaSwapAdapter cannot execute these —" >&2
76+
echo " investigate them as potential adapter/API compatibility findings before trusting the" >&2
77+
echo " regenerated fixtures. Dropped quotes:" >&2
78+
jq -c '[ .[]
79+
| select((.signature == null) or (.sigExpiration == null)
80+
or ((.trade.data | ascii_downcase | startswith("0x5f575529")) | not))
81+
| { aggregator: .quote.bridgeId,
82+
quoteId: .quoteId,
83+
signed: (.signature != null and .sigExpiration != null),
84+
tradeTo: .trade.to,
85+
tradeDataSelector: (.trade.data[0:10]) }
86+
] | .[]' "${raw}" >&2
87+
fi
88+
89+
jq \
90+
--arg pair "${pair}" \
91+
--arg src "${src}" \
92+
--arg dest "${dest}" \
93+
--arg amount "${amount}" \
94+
'[ .[]
95+
| select(.signature != null and .sigExpiration != null)
96+
| select(.trade.data | ascii_downcase | startswith("0x5f575529")) # IMetaSwap.swap selector only
97+
| {
98+
pair: $pair,
99+
aggregator: .quote.bridgeId,
100+
quoteId: .quoteId,
101+
srcToken: $src,
102+
destToken: $dest,
103+
srcTokenAmount: $amount,
104+
destTokenAmount: (.quote.destTokenAmount | tostring),
105+
minDestTokenAmount: (.quote.minDestTokenAmount | tostring),
106+
tradeTo: .trade.to,
107+
tradeValue: (.trade.value | tostring),
108+
apiData: .trade.data,
109+
sigExpiration: .sigExpiration,
110+
signature: .signature
111+
}
112+
]' "${raw}"
113+
}
114+
115+
fetch_pair "ERC20_TO_ERC20" "${USDC}" "${WETH}" "${USDC_AMOUNT}" > "${TMP_DIR}/erc20_to_erc20.json"
116+
fetch_pair "NATIVE_TO_ERC20" "${NATIVE}" "${USDC}" "${NATIVE_AMOUNT}" > "${TMP_DIR}/native_to_erc20.json"
117+
fetch_pair "ERC20_TO_NATIVE" "${USDC}" "${NATIVE}" "${USDC_AMOUNT}" > "${TMP_DIR}/erc20_to_native.json"
118+
119+
# Capture the current Linea block: this becomes the fork pin for the fork tests (see header note).
120+
echo "Fetching current Linea block number..." >&2
121+
LINEA_BLOCK="$(cast block-number --rpc-url "${LINEA_RPC_URL}")"
122+
123+
jq -n \
124+
--arg source "${API_BASE}?signQuotes=true" \
125+
--argjson block "${LINEA_BLOCK}" \
126+
--argjson chainId "${CHAIN_ID}" \
127+
--arg walletAddress "${WALLET_ADDRESS}" \
128+
--arg queryParams "${QUERY_PARAMS}" \
129+
--arg usdc "${USDC}" \
130+
--arg weth "${WETH}" \
131+
--slurpfile a "${TMP_DIR}/erc20_to_erc20.json" \
132+
--slurpfile b "${TMP_DIR}/native_to_erc20.json" \
133+
--slurpfile c "${TMP_DIR}/erc20_to_native.json" \
134+
'{
135+
meta: {
136+
source: $source,
137+
fetchedAtLineaBlock: $block,
138+
chainId: $chainId,
139+
# SIGNER PIN: this is the swap API signing key observed on bridge.dev-api.cx.metamask.io (a DEV
140+
# environment whose key can rotate independently of production). The generator re-derives the signer
141+
# from every fresh signature and FAILS if it no longer matches this pin. If that happens, the dev
142+
# API key most likely rotated: update this constant (and re-check the deployed swapApiSigner) rather
143+
# than suspecting a contract incompatibility. InvalidApiSignature failures after a refresh usually
144+
# mean key rotation, not adapter breakage.
145+
expectedSigner: "0xe672B534ccf9876a7554a1dD1685a2a5C2Cc8e8C",
146+
metaSwap: "0x9dDA6Ef3D919c9bC8885D5560999A3640431e8e6",
147+
delegationManager: "0x739309deED0Ae184E66a427ACa432aE1D91d022e",
148+
hybridDeleGatorImpl: "0xf4E57F579ad8169D0d4Da7AedF71AC3f83e8D2b4",
149+
entryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
150+
walletAddressUsedInQuote: $walletAddress,
151+
queryParams: $queryParams,
152+
usdc: $usdc,
153+
weth: $weth
154+
},
155+
quotes: ($a[0] + $b[0] + $c[0])
156+
}' > "${OUT_JSON}"
157+
158+
QUOTE_COUNT="$(jq '.quotes | length' "${OUT_JSON}")"
159+
echo "Wrote ${OUT_JSON} (${QUOTE_COUNT} signed quotes, Linea block ${LINEA_BLOCK})" >&2
160+
161+
if [ "${QUOTE_COUNT}" -ne 15 ]; then
162+
echo "warning: expected 15 quotes (3 pairs x 5 aggregators), got ${QUOTE_COUNT}." >&2
163+
echo " The aggregator set returned by the API may have changed; if so, update the fork test" >&2
164+
echo " function list in test/helpers/DelegationMetaSwapAdapterNewApi.t.sol to match the new" >&2
165+
echo " fixture getters." >&2
166+
fi
167+
168+
python3 "${REPO_ROOT}/scripts/generate_new_api_fixtures.py" "${OUT_JSON}"
169+
170+
echo "Done. Run 'forge fmt && forge build' and then the test suites:" >&2
171+
echo " forge test --match-contract DelegationMetaSwapAdapterNewApiSignatureCompatTest" >&2
172+
echo " LINEA_RPC_URL=<archive rpc> forge test --match-contract DelegationMetaSwapAdapterNewApiForkTest" >&2

0 commit comments

Comments
 (0)