Skip to content

Commit f6f8807

Browse files
authored
fix(ZkSync_SpokePool): Add missing bridged USDC approval (#967)
An approval is required to initiate a withdrawal of Circle bridged (upgradable) USDC on Lens.
1 parent 77761d7 commit f6f8807

File tree

7 files changed

+59
-10
lines changed

7 files changed

+59
-10
lines changed

.github/workflows/pr.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ jobs:
2828
- name: Install Cargo toolchain
2929
uses: actions-rs/toolchain@v1
3030
with:
31-
toolchain: nightly
31+
toolchain: nightly-2025-04-01
3232
profile: minimal
3333
components: rustc
3434
- name: Install packages
35-
run: yarn install --frozen-lockfile && rustup component add --toolchain nightly-x86_64-unknown-linux-gnu rustfmt
35+
run: yarn install --frozen-lockfile && rustup component add --toolchain nightly-2025-04-01-x86_64-unknown-linux-gnu rustfmt
3636
- name: Lint js
3737
shell: bash
3838
run: yarn lint-js
@@ -69,7 +69,7 @@ jobs:
6969
- name: Install Cargo toolchain
7070
uses: actions-rs/toolchain@v1
7171
with:
72-
toolchain: stable
72+
toolchain: nightly-2025-04-01
7373
profile: minimal
7474
components: rustc
7575
- name: Cache Cargo dependencies

contracts/ZkSync_SpokePool.sol

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity ^0.8.0;
33

4-
import "./SpokePool.sol";
4+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
55
import { CircleCCTPAdapter, CircleDomainIds, ITokenMessenger } from "./libraries/CircleCCTPAdapter.sol";
66
import { CrossDomainAddressUtils } from "./libraries/CrossDomainAddressUtils.sol";
7+
import "./SpokePool.sol";
78

89
// https://github.com/matter-labs/era-contracts/blob/6391c0d7bf6184d7f6718060e3991ba6f0efe4a7/zksync/contracts/bridge/L2ERC20Bridge.sol#L104
910
interface ZkBridgeLike {
@@ -24,6 +25,8 @@ interface IL2ETH {
2425
* @custom:security-contact [email protected]
2526
*/
2627
contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
28+
using SafeERC20 for IERC20;
29+
2730
// On Ethereum, avoiding constructor parameters and putting them into constants reduces some of the gas cost
2831
// upon contract deployment. On zkSync the opposite is true: deploying the same bytecode for contracts,
2932
// while changing only constructor parameters can lead to substantial fee savings. So, the following params
@@ -151,6 +154,7 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
151154
_transferUsdc(withdrawalRecipient, amountToReturn);
152155
} else {
153156
// Matter Labs custom USDC bridge for Circle Bridged (upgradable) USDC.
157+
IERC20(l2TokenAddress).forceApprove(address(zkUSDCBridge), amountToReturn);
154158
zkUSDCBridge.withdraw(withdrawalRecipient, l2TokenAddress, amountToReturn);
155159
}
156160
} else {

package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,25 @@
2222
"lint": "yarn lint-solidity && yarn lint-js && yarn lint-rust",
2323
"lint-solidity": "yarn solhint ./contracts/**/*.sol",
2424
"lint-js": "yarn prettier --list-different **/*.js **/*.ts",
25-
"lint-rust": "cargo +nightly fmt --all -- --check && cargo clippy",
26-
"lint-fix": "yarn prettier --write **/*.js **/*.ts ./programs/**/*.rs ./contracts**/*.sol && cargo +nightly fmt --all && cargo clippy",
25+
"lint-rust": "cargo +nightly-2025-04-01 fmt --all -- --check && cargo clippy",
26+
"lint-fix": "yarn prettier --write **/*.js **/*.ts ./programs/**/*.rs ./contracts**/*.sol && cargo +nightly-2025-04-01 fmt --all && cargo clippy",
2727
"clean-fast": "for dir in node_modules cache cache-zk artifacts artifacts-zk dist typechain; do mv \"${dir}\" \"_${dir}\"; rm -rf \"_${dir}\" &; done",
2828
"clean": "rm -rf node_modules cache cache-zk artifacts artifacts-zk dist typechain",
2929
"generate-svm-assets": "sh ./scripts/generate-svm-assets.sh && yarn generate-svm-clients",
3030
"generate-svm-clients": "yarn ts-node ./scripts/svm/utils/generate-svm-clients.ts && yarn ts-node ./scripts/svm/utils/rename-clients-imports.ts",
3131
"build-evm": "hardhat compile",
32-
"build-svm": "echo 'Generating IDLs...' && anchor build > /dev/null 2>&1 || true && anchor run generateExternalTypes && anchor build",
32+
"build-svm": "echo 'Generating IDLs...' && yarn anchor-build > /dev/null 2>&1 || true && anchor run generateExternalTypes && yarn anchor-build",
3333
"build-ts": "rm -rf ./dist && tsc && rsync -a --include '*/' --include '*.d.ts' --exclude '*' ./typechain ./dist/",
3434
"copy-idl": "mkdir -p dist/src/svm/assets/idl && cp src/svm/assets/idl/*.json dist/src/svm/assets/idl/",
3535
"build": "yarn build-evm && yarn build-svm && yarn generate-svm-assets && yarn build-ts && yarn copy-idl",
3636
"test-evm": "IS_TEST=true hardhat test",
37-
"test-svm": "anchor test -- --features test",
37+
"test-svm": "sh ./scripts/anchor-test.sh",
3838
"test": "yarn test-evm && yarn test-svm",
3939
"test:report-gas": "IS_TEST=true REPORT_GAS=true hardhat test",
4040
"generate-evm-assets": "rm -rf typechain && TYPECHAIN=ethers yarn hardhat typechain",
4141
"prepublish": "yarn build && hardhat export --export-all ./cache/massExport.json && ts-node ./scripts/processHardhatExport.ts && prettier --write ./deployments/deployments.json && yarn generate-evm-assets",
42-
"pre-commit-hook": "sh scripts/preCommitHook.sh"
42+
"pre-commit-hook": "sh scripts/preCommitHook.sh",
43+
"anchor-build": "sh ./scripts/anchor-build.sh"
4344
},
4445
"dependencies": {
4546
"@across-protocol/constants": "^3.1.46",

rust-toolchain.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[toolchain]
2+
channel = "nightly-2025-04-01"

scripts/anchor-build.sh

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/sh
2+
3+
echo "🔨 Building anchor programs (no IDL)..."
4+
anchor build --no-idl
5+
6+
echo "📦 Generating IDLs (using nightly)..."
7+
8+
for program in programs/*; do
9+
[ -d "$program" ] || continue
10+
11+
dir_name=$(basename "$program")
12+
program_name=$(echo "$dir_name" | tr '-' '_')
13+
14+
echo "→ IDL for $program_name"
15+
RUSTUP_TOOLCHAIN="nightly-2025-04-01" anchor idl build -p "$dir_name" -o "target/idl/$program_name.json" -t "target/types/$program_name.ts"
16+
done

scripts/anchor-test.sh

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/sh
2+
3+
echo "🔨 Building anchor programs (with test feature, no IDL)..."
4+
anchor build --no-idl -- --features test
5+
6+
echo "📦 Generating IDLs (using nightly)..."
7+
8+
for program in programs/*; do
9+
[ -d "$program" ] || continue
10+
11+
dir_name=$(basename "$program")
12+
program_name=$(echo "$dir_name" | tr '-' '_')
13+
14+
echo "→ IDL for $program_name"
15+
RUSTUP_TOOLCHAIN="nightly-2025-04-01" anchor idl build -p "$dir_name" -o "target/idl/$program_name.json" -t "target/types/$program_name.ts"
16+
done
17+
18+
echo "🧪 Running anchor tests..."
19+
anchor test --skip-build

test/evm/hardhat/chain-specific-spokepools/ZkSync_SpokePool.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,16 @@ describe("ZkSync Spoke Pool", function () {
202202
await zkSyncSpokePool.callStatic.chainId()
203203
);
204204
await zkSyncSpokePool.connect(crossDomainAlias).relayRootBundle(tree.getHexRoot(), mockTreeRoot);
205+
let allowance = await usdc.allowance(zkSyncSpokePool.address, zkUSDCBridge.address);
206+
expect(allowance.isZero()).to.be.true;
207+
205208
await zkSyncSpokePool.connect(relayer).executeRelayerRefundLeaf(0, leaves[0], tree.getHexProof(leaves[0]));
206209

207-
// This should have sent tokens back to L1. Check the correct methods on the gateway are correctly called.
210+
// This should have called withdraw() to pull tokens back to L1. Check the correct methods on the gateway are correctly called.
211+
// zkUSDCBridge is a mocked contract, so the tokens are not actually moved and the approval is intact.
212+
allowance = await usdc.allowance(zkSyncSpokePool.address, zkUSDCBridge.address);
213+
expect(allowance.eq(amountToReturn)).to.be.true;
214+
208215
expect(zkUSDCBridge.withdraw).to.have.been.calledOnce;
209216
expect(zkUSDCBridge.withdraw).to.have.been.calledWith(hubPool.address, usdc.address, amountToReturn);
210217
});

0 commit comments

Comments
 (0)