Skip to content

Commit 6e08b75

Browse files
committed
Merge remote-tracking branch 'origin/audit-zkstack-usdc' into pxrl/n01-zksync
2 parents fe6dc3e + f6f8807 commit 6e08b75

File tree

2 files changed

+13
-2
lines changed

2 files changed

+13
-2
lines changed

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
@@ -152,6 +155,7 @@ contract ZkSync_SpokePool is SpokePool, CircleCCTPAdapter {
152155
_transferUsdc(withdrawalRecipient, amountToReturn);
153156
} else {
154157
// Matter Labs custom USDC bridge for Circle Bridged (upgradable) USDC.
158+
IERC20(l2TokenAddress).forceApprove(address(zkUSDCBridge), amountToReturn);
155159
zkUSDCBridge.withdraw(withdrawalRecipient, l2TokenAddress, amountToReturn);
156160
}
157161
} else {

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)