mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-12 07:28:08 -05:00
Compare commits
11 Commits
v4.4.89
...
usdc_gatew
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
782078834d | ||
|
|
e8eb1e7264 | ||
|
|
5b6cd3fdfb | ||
|
|
4e76fb0976 | ||
|
|
b457094247 | ||
|
|
33f572deb3 | ||
|
|
8ccf3b16dd | ||
|
|
ae694267fc | ||
|
|
1f383ca2f5 | ||
|
|
2bf31f046e | ||
|
|
5b5b45bebb |
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"ProxyAdmin": null,
|
||||
"ProxyAdmin": "0x95ec888F34b23063aeaB47c8E229B3a25DBF8e4F",
|
||||
"ZKRollup": {
|
||||
"implementation": null,
|
||||
"proxy": null
|
||||
},
|
||||
"L1ScrollMessenger": {
|
||||
"implementation": null,
|
||||
"proxy": null
|
||||
"implementation": "0xC71532468A74084cfd824c0445E96f9A2dc3Bd7E",
|
||||
"proxy": "0x50c7d3e7f7c656493D1D76aaa1a836CedfCBB16A"
|
||||
},
|
||||
"L1GatewayRouter": {
|
||||
"implementation": null,
|
||||
"proxy": null
|
||||
"implementation": "0x5431937CF9cb638df5e3587Ae0a2F62130CEE27e",
|
||||
"proxy": "0x13FBE0D0e5552b8c9c4AE9e2435F38f37355998a"
|
||||
},
|
||||
"L1StandardERC20Gateway": {
|
||||
"implementation": null,
|
||||
@@ -19,5 +19,9 @@
|
||||
"L1WETHGateway": {
|
||||
"implementation": null,
|
||||
"proxy": null
|
||||
},
|
||||
"L1USDCGateway": {
|
||||
"implementation": "0x2Cf090069Bc47CA931f350fe1D0a160dba4c7C53",
|
||||
"proxy": "0xeED47C513265cefe6846Dd51B624F1102A9a89d3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"ProxyAdmin": null,
|
||||
"ProxyAdmin": "0x95ec888F34b23063aeaB47c8E229B3a25DBF8e4F",
|
||||
"WETH": null,
|
||||
"Whitelist": null,
|
||||
"ScrollStandardERC20": null,
|
||||
"ScrollStandardERC20Factory": null,
|
||||
"L2ScrollMessenger": null,
|
||||
"L2ScrollMessenger": {
|
||||
"implementation": "0x45BA70424D61e6A0D2A5FF9093927350471A2728",
|
||||
"proxy": "0xBa50f5340FB9F3Bd074bD638c9BE13eCB36E603d"
|
||||
},
|
||||
"L2GatewayRouter": {
|
||||
"implementation": null,
|
||||
"proxy": null
|
||||
"implementation": "0x0378D0F56f13f018b8d4803f09349781e143453e",
|
||||
"proxy": "0x9aD3c5617eCAa556d6E166787A97081907171230"
|
||||
},
|
||||
"L2StandardERC20Gateway": {
|
||||
"implementation": null,
|
||||
@@ -16,5 +19,9 @@
|
||||
"L2WETHGateway": {
|
||||
"implementation": null,
|
||||
"proxy": null
|
||||
},
|
||||
"L2USDCGateway": {
|
||||
"implementation": "0xCad8ba59173F3d6b3d15E8eeE2CB5C8b43e50fC0",
|
||||
"proxy": "0x78a5dacf40E26c21A69fA3D701F9e75f19FD7113"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,14 +60,10 @@ const config: HardhatUserConfig = {
|
||||
},
|
||||
l1geth: {
|
||||
url: SCROLL_L1_RPC,
|
||||
gasPrice: 20000000000,
|
||||
gasMultiplier: 1.1,
|
||||
accounts: [L1_DEPLOYER_PRIVATE_KEY],
|
||||
},
|
||||
l2geth: {
|
||||
url: SCROLL_L2_RPC,
|
||||
gasPrice: 20000000000,
|
||||
gasMultiplier: 1.1,
|
||||
accounts: [L2_DEPLOYER_PRIVATE_KEY],
|
||||
},
|
||||
},
|
||||
@@ -83,6 +79,16 @@ const config: HardhatUserConfig = {
|
||||
},
|
||||
etherscan: {
|
||||
apiKey: process.env.ETHERSCAN_API_KEY,
|
||||
customChains: [
|
||||
{
|
||||
network: "l2geth",
|
||||
chainId: 534351,
|
||||
urls: {
|
||||
apiURL: "https://sepolia-blockscout.scroll.io/api",
|
||||
browserURL: "https://sepolia-blockscout.scroll.io",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
dodoc: {
|
||||
runOnCompile: true,
|
||||
|
||||
@@ -21,7 +21,7 @@ async function main() {
|
||||
if (!addressFile.get(`${contractName}.implementation`)) {
|
||||
console.log(`>> Deploy ${contractName} implementation`);
|
||||
const ContractImpl = await ethers.getContractFactory(contractName, deployer);
|
||||
const impl = await ContractImpl.deploy();
|
||||
const impl = await ContractImpl.deploy(process.env.L1_USDC_ADDR, process.env.L2_USDC_ADDR);
|
||||
console.log(`>> waiting for transaction: ${impl.deployTransaction.hash}`);
|
||||
await impl.deployed();
|
||||
console.log(`✅ ${contractName} implementation deployed at ${impl.address}`);
|
||||
|
||||
@@ -25,18 +25,18 @@ async function main() {
|
||||
const L2StandardERC20Impl = process.env.L2_SCROLL_STANDARD_ERC20_ADDR!;
|
||||
const L2StandardERC20FactoryAddress = process.env.L2_SCROLL_STANDARD_ERC20_FACTORY_ADDR!;
|
||||
|
||||
// if ((await L1StandardERC20Gateway.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L1StandardERC20Gateway.initialize(
|
||||
L2StandardERC20GatewayAddress,
|
||||
L1GatewayRouterAddress,
|
||||
L1ScrollMessengerAddress,
|
||||
L2StandardERC20Impl,
|
||||
L2StandardERC20FactoryAddress
|
||||
);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
// }
|
||||
if ((await L1StandardERC20Gateway.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L1StandardERC20Gateway.initialize(
|
||||
L2StandardERC20GatewayAddress,
|
||||
L1GatewayRouterAddress,
|
||||
L1ScrollMessengerAddress,
|
||||
L2StandardERC20Impl,
|
||||
L2StandardERC20FactoryAddress
|
||||
);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
|
||||
@@ -23,16 +23,16 @@ async function main() {
|
||||
const L1ScrollMessengerAddress = addressFile.get("L1ScrollMessenger.proxy");
|
||||
const L2GatewayRouterAddress = process.env.L2_GATEWAY_ROUTER_PROXY_ADDR!;
|
||||
|
||||
// if ((await L1GatewayRouter.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L1GatewayRouter.initialize(
|
||||
L1StandardERC20GatewayAddress,
|
||||
L2GatewayRouterAddress,
|
||||
L1ScrollMessengerAddress
|
||||
);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
// }
|
||||
if ((await L1GatewayRouter.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L1GatewayRouter.initialize(
|
||||
L1StandardERC20GatewayAddress,
|
||||
L2GatewayRouterAddress,
|
||||
L1ScrollMessengerAddress
|
||||
);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
|
||||
@@ -21,12 +21,12 @@ async function main() {
|
||||
|
||||
const ZKRollupAddress = addressFile.get("ZKRollup.proxy");
|
||||
|
||||
// if ((await L1ScrollMessenger.rollup()) === constants.AddressZero) {
|
||||
const tx = await L1ScrollMessenger.initialize(ZKRollupAddress);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
// }
|
||||
if ((await L1ScrollMessenger.rollup()) === constants.AddressZero) {
|
||||
const tx = await L1ScrollMessenger.initialize(ZKRollupAddress);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
|
||||
35
contracts/scripts/initialize_l1_usdc_gateway.ts
Normal file
35
contracts/scripts/initialize_l1_usdc_gateway.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable node/no-missing-import */
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
import { constants } from "ethers";
|
||||
import * as hre from "hardhat";
|
||||
import { ethers } from "hardhat";
|
||||
import { selectAddressFile } from "./utils";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function main() {
|
||||
const addressFile = selectAddressFile(hre.network.name);
|
||||
|
||||
const [deployer] = await ethers.getSigners();
|
||||
|
||||
const L1USDCGateway = await ethers.getContractAt("L1USDCGateway", addressFile.get("L1USDCGateway.proxy"), deployer);
|
||||
|
||||
const L1GatewayRouterAddress = addressFile.get("L1GatewayRouter.proxy");
|
||||
const L1ScrollMessengerAddress = addressFile.get("L1ScrollMessenger.proxy");
|
||||
const L2USDCGatewayAddress = process.env.L2_USDC_GATEWAY_PROXY_ADDR!;
|
||||
|
||||
if ((await L1USDCGateway.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L1USDCGateway.initialize(L2USDCGatewayAddress, L1GatewayRouterAddress, L1ScrollMessengerAddress);
|
||||
console.log("initialize L1USDCGateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
// and properly handle errors.
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -24,17 +24,17 @@ async function main() {
|
||||
const L2StandardERC20FactoryAddress = addressFile.get("ScrollStandardERC20Factory");
|
||||
const L1StandardERC20GatewayAddress = process.env.L1_STANDARD_ERC20_GATEWAY_PROXY_ADDR!;
|
||||
|
||||
// if ((await L2StandardERC20Gateway.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L2StandardERC20Gateway.initialize(
|
||||
L1StandardERC20GatewayAddress,
|
||||
L2GatewayRouterAddress,
|
||||
L2ScrollMessengerAddress,
|
||||
L2StandardERC20FactoryAddress
|
||||
);
|
||||
console.log("initialize L2StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
// }
|
||||
if ((await L2StandardERC20Gateway.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L2StandardERC20Gateway.initialize(
|
||||
L1StandardERC20GatewayAddress,
|
||||
L2GatewayRouterAddress,
|
||||
L2ScrollMessengerAddress,
|
||||
L2StandardERC20FactoryAddress
|
||||
);
|
||||
console.log("initialize L2StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
|
||||
@@ -23,16 +23,16 @@ async function main() {
|
||||
const L2ScrollMessengerAddress = addressFile.get("L2ScrollMessenger");
|
||||
const L1GatewayRouterAddress = process.env.L1_GATEWAY_ROUTER_PROXY_ADDR!;
|
||||
|
||||
// if ((await L2GatewayRouter.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L2GatewayRouter.initialize(
|
||||
L2StandardERC20GatewayAddress,
|
||||
L1GatewayRouterAddress,
|
||||
L2ScrollMessengerAddress
|
||||
);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
// }
|
||||
if ((await L2GatewayRouter.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L2GatewayRouter.initialize(
|
||||
L2StandardERC20GatewayAddress,
|
||||
L1GatewayRouterAddress,
|
||||
L2ScrollMessengerAddress
|
||||
);
|
||||
console.log("initialize L1StandardERC20Gateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
|
||||
35
contracts/scripts/initialize_l2_usdc_gateway.ts
Normal file
35
contracts/scripts/initialize_l2_usdc_gateway.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable node/no-missing-import */
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
import { constants } from "ethers";
|
||||
import * as hre from "hardhat";
|
||||
import { ethers } from "hardhat";
|
||||
import { selectAddressFile } from "./utils";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function main() {
|
||||
const addressFile = selectAddressFile(hre.network.name);
|
||||
|
||||
const [deployer] = await ethers.getSigners();
|
||||
|
||||
const L2USDCGateway = await ethers.getContractAt("L2USDCGateway", addressFile.get("L2USDCGateway.proxy"), deployer);
|
||||
|
||||
const L2GatewayRouterAddress = addressFile.get("L2GatewayRouter.proxy");
|
||||
const L2ScrollMessengerAddress = addressFile.get("L2ScrollMessenger.proxy");
|
||||
const L1USDCGatewayAddress = process.env.L1_USDC_GATEWAY_PROXY_ADDR!;
|
||||
|
||||
if ((await L2USDCGateway.counterpart()) === constants.AddressZero) {
|
||||
const tx = await L2USDCGateway.initialize(L1USDCGatewayAddress, L2GatewayRouterAddress, L2ScrollMessengerAddress);
|
||||
console.log("initialize L2USDCGateway, hash:", tx.hash);
|
||||
const receipt = await tx.wait();
|
||||
console.log(`✅ Done, gas used: ${receipt.gasUsed}`);
|
||||
}
|
||||
}
|
||||
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
// and properly handle errors.
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -68,7 +68,7 @@ abstract contract L1ERC20Gateway is IL1ERC20Gateway, IMessageDropCallback, Scrol
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart nonReentrant {
|
||||
) external payable virtual override onlyCallByCounterpart nonReentrant {
|
||||
_beforeFinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
|
||||
// @note can possible trigger reentrant call to this contract or messenger,
|
||||
|
||||
@@ -2,10 +2,163 @@
|
||||
|
||||
pragma solidity =0.8.16;
|
||||
|
||||
import {L1CustomERC20Gateway} from "../L1CustomERC20Gateway.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
import {IFiatToken} from "../../../interfaces/IFiatToken.sol";
|
||||
import {IUSDCBurnableSourceBridge} from "../../../interfaces/IUSDCBurnableSourceBridge.sol";
|
||||
import {IL2ERC20Gateway} from "../../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import {IL1ScrollMessenger} from "../../IL1ScrollMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "../IL1ERC20Gateway.sol";
|
||||
|
||||
contract L1USDCGateway is L1CustomERC20Gateway {
|
||||
import {ScrollGatewayBase} from "../../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {L1ERC20Gateway} from "../L1ERC20Gateway.sol";
|
||||
|
||||
/// @title L1USDCGateway
|
||||
/// @notice The `L1USDCGateway` contract is used to deposit `USDC` token in layer 1 and
|
||||
/// finalize withdraw `USDC` from layer 2, before USDC become native in layer 2.
|
||||
contract L1USDCGateway is L1ERC20Gateway, IUSDCBurnableSourceBridge {
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L1 USDC address.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
address public immutable l1USDC;
|
||||
|
||||
/// @notice The address of L2 USDC address.
|
||||
address public immutable l2USDC;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
address public circleCaller;
|
||||
|
||||
bool public depositPaused;
|
||||
|
||||
bool public withdrawPaused;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(address _l1USDC, address _l2USDC) {
|
||||
_disableInitializers();
|
||||
|
||||
l1USDC = _l1USDC;
|
||||
l2USDC = _l2USDC;
|
||||
}
|
||||
|
||||
/// @notice Initialize the storage of L1WETHGateway.
|
||||
/// @param _counterpart The address of L2ETHGateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address) public view override returns (address) {
|
||||
return l2USDC;
|
||||
}
|
||||
|
||||
/*******************************
|
||||
* Public Restricted Functions *
|
||||
*******************************/
|
||||
|
||||
/// @inheritdoc IUSDCBurnableSourceBridge
|
||||
function burnAllLockedUSDC() external override {
|
||||
require(msg.sender == circleCaller, "only circle caller");
|
||||
|
||||
uint256 _balance = IERC20Upgradeable(l1USDC).balanceOf(address(this));
|
||||
require(IFiatToken(l1USDC).burn(_balance), "burn USDC failed");
|
||||
}
|
||||
|
||||
/// @notice Update the Circle EOA address.
|
||||
/// @param _caller The address to update.
|
||||
function updateCircleCaller(address _caller) external onlyOwner {
|
||||
circleCaller = _caller;
|
||||
}
|
||||
|
||||
/// @notice Change the deposit pause status of this contract.
|
||||
/// @param _paused The new status, `true` means paused and `false` means not paused.
|
||||
function pauseDeposit(bool _paused) external onlyOwner {
|
||||
depositPaused = _paused;
|
||||
}
|
||||
|
||||
/// @notice Change the withdraw pause status of this contract.
|
||||
/// @param _paused The new status, `true` means paused and `false` means not paused.
|
||||
function pauseWithdraw(bool _paused) external onlyOwner {
|
||||
withdrawPaused = _paused;
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _beforeFinalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) internal virtual override {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
require(_l1Token == l1USDC, "l1 token not USDC");
|
||||
require(_l2Token == l2USDC, "l2 token not USDC");
|
||||
require(!withdrawPaused, "withdraw paused");
|
||||
}
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _beforeDropMessage(
|
||||
address,
|
||||
address,
|
||||
uint256
|
||||
) internal virtual override {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
}
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
require(_token == l1USDC, "only USDC is allowed");
|
||||
require(!depositPaused, "deposit paused");
|
||||
|
||||
// 1. Transfer token into this contract.
|
||||
address _from;
|
||||
(_from, _amount, _data) = _transferERC20In(_token, _amount, _data);
|
||||
require(_data.length == 0, "call is not allowed");
|
||||
|
||||
// 2. Generate message passed to L2USDCGateway.
|
||||
bytes memory _message = abi.encodeCall(
|
||||
IL2ERC20Gateway.finalizeDepositERC20,
|
||||
(_token, l2USDC, _from, _to, _amount, _data)
|
||||
);
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit, _from);
|
||||
|
||||
emit DepositERC20(_token, l2USDC, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
|
||||
184
contracts/src/L1/gateways/usdc/L1USDCGatewayCCTP.sol
Normal file
184
contracts/src/L1/gateways/usdc/L1USDCGatewayCCTP.sol
Normal file
@@ -0,0 +1,184 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity =0.8.16;
|
||||
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
|
||||
import {ITokenMessenger} from "../../../interfaces/ITokenMessenger.sol";
|
||||
import {IL2ERC20Gateway} from "../../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import {IL1ScrollMessenger} from "../../IL1ScrollMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "../IL1ERC20Gateway.sol";
|
||||
|
||||
import {CCTPGatewayBase} from "../../../libraries/gateway/CCTPGatewayBase.sol";
|
||||
import {ScrollGatewayBase} from "../../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {L1ERC20Gateway} from "../L1ERC20Gateway.sol";
|
||||
|
||||
/// @title L1USDCGatewayCCTP
|
||||
/// @notice The `L1USDCGateway` contract is used to deposit `USDC` token in layer 1 and
|
||||
/// finalize withdraw `USDC` from layer 2, after USDC become native in layer 2.
|
||||
contract L1USDCGatewayCCTP is CCTPGatewayBase, L1ERC20Gateway {
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(
|
||||
address _l1USDC,
|
||||
address _l2USDC,
|
||||
uint32 _destinationDomain
|
||||
) CCTPGatewayBase(_l1USDC, _l2USDC, _destinationDomain) {}
|
||||
|
||||
/// @notice Initialize the storage of L1USDCGatewayCCTP.
|
||||
/// @param _counterpart The address of L2USDCGatewayCCTP in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
/// @param _cctpMessenger The address of TokenMessenger in local domain.
|
||||
/// @param _cctpTransmitter The address of MessageTransmitter in local domain.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger,
|
||||
address _cctpMessenger,
|
||||
address _cctpTransmitter
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
CCTPGatewayBase._initialize(_cctpMessenger, _cctpTransmitter);
|
||||
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address) public view override returns (address) {
|
||||
return l2USDC;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Relay cross chain message and claim USDC that has been cross chained.
|
||||
/// @dev The `_scrollMessage` is actually encoded calldata for `L1ScrollMessenger.relayMessageWithProof`.
|
||||
///
|
||||
/// @dev This helper function is aimed to claim USDC in single transaction.
|
||||
/// Normally, an user should call `L1ScrollMessenger.relayMessageWithProof` first,
|
||||
/// then `L1USDCGatewayCCTP.claimUSDC`.
|
||||
///
|
||||
/// @param _nonce The nonce of the message from CCTP.
|
||||
/// @param _cctpMessage The message passed to MessageTransmitter contract in CCTP.
|
||||
/// @param _cctpSignature The message passed to MessageTransmitter contract in CCTP.
|
||||
/// @param _scrollMessage The message passed to L1ScrollMessenger contract.
|
||||
function relayAndClaimUSDC(
|
||||
uint256 _nonce,
|
||||
bytes calldata _cctpMessage,
|
||||
bytes calldata _cctpSignature,
|
||||
bytes calldata _scrollMessage
|
||||
) external {
|
||||
require(status[_nonce] == CCTPMessageStatus.None, "message relayed");
|
||||
// call messenger to set `status[_nonce]` to `CCTPMessageStatus.Pending`.
|
||||
(bool _success, ) = messenger.call(_scrollMessage);
|
||||
require(_success, "call messenger failed");
|
||||
|
||||
claimUSDC(_nonce, _cctpMessage, _cctpSignature);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
/// @dev The function will not mint the USDC, users need to call `claimUSDC` after this function is done.
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
require(_l1Token == l1USDC, "l1 token not USDC");
|
||||
require(_l2Token == l2USDC, "l2 token not USDC");
|
||||
|
||||
uint256 _nonce;
|
||||
(_nonce, _data) = abi.decode(_data, (uint256, bytes));
|
||||
require(status[_nonce] == CCTPMessageStatus.None, "message relayed");
|
||||
status[_nonce] = CCTPMessageStatus.Pending;
|
||||
|
||||
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/*******************************
|
||||
* Public Restricted Functions *
|
||||
*******************************/
|
||||
|
||||
/// @notice Update the CCTP contract addresses.
|
||||
/// @param _messenger The address of TokenMessenger in local domain.
|
||||
/// @param _transmitter The address of MessageTransmitter in local domain.
|
||||
function updateCCTPContracts(address _messenger, address _transmitter) external onlyOwner {
|
||||
cctpMessenger = _messenger;
|
||||
cctpTransmitter = _transmitter;
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _beforeFinalizeWithdrawERC20(
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) internal virtual override {}
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _beforeDropMessage(
|
||||
address,
|
||||
address,
|
||||
uint256
|
||||
) internal virtual override {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
}
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
require(_token == l1USDC, "only USDC is allowed");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from;
|
||||
(_from, _amount, _data) = _transferERC20In(_token, _amount, _data);
|
||||
|
||||
// 2. Burn token through CCTP TokenMessenger
|
||||
uint256 _nonce = ITokenMessenger(cctpMessenger).depositForBurnWithCaller(
|
||||
_amount,
|
||||
destinationDomain,
|
||||
bytes32(uint256(uint160(_to))),
|
||||
address(this),
|
||||
bytes32(uint256(uint160(counterpart)))
|
||||
);
|
||||
|
||||
// 3. Generate message passed to L2USDCGatewayCCTP.
|
||||
bytes memory _message = abi.encodeCall(
|
||||
IL2ERC20Gateway.finalizeDepositERC20,
|
||||
(_token, l2USDC, _from, _to, _amount, abi.encode(_nonce, _data))
|
||||
);
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit DepositERC20(_token, l2USDC, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20
|
||||
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
|
||||
import {IFiatToken} from "../../../interfaces/IFiatToken.sol";
|
||||
import {IUSDCDestinationBridge} from "../../../interfaces/IUSDCDestinationBridge.sol";
|
||||
import {IL1ERC20Gateway} from "../../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../../IL2ScrollMessenger.sol";
|
||||
import {IL2ERC20Gateway} from "../IL2ERC20Gateway.sol";
|
||||
@@ -16,7 +17,7 @@ import {L2ERC20Gateway} from "../L2ERC20Gateway.sol";
|
||||
/// @title L2USDCGateway
|
||||
/// @notice The `L2USDCGateway` contract is used to withdraw `USDC` token on layer 2 and
|
||||
/// finalize deposit `USDC` from layer 1.
|
||||
contract L2USDCGateway is L2ERC20Gateway {
|
||||
contract L2USDCGateway is L2ERC20Gateway, IUSDCDestinationBridge {
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/*************
|
||||
@@ -33,6 +34,8 @@ contract L2USDCGateway is L2ERC20Gateway {
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
address public circleCaller;
|
||||
|
||||
bool public depositPaused;
|
||||
|
||||
bool public withdrawPaused;
|
||||
@@ -91,7 +94,8 @@ contract L2USDCGateway is L2ERC20Gateway {
|
||||
|
||||
require(IFiatToken(_l2Token).mint(_to, _amount), "mint USDC failed");
|
||||
|
||||
_doCallback(_to, _data);
|
||||
// disable call for USDC
|
||||
// _doCallback(_to, _data);
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
@@ -100,6 +104,19 @@ contract L2USDCGateway is L2ERC20Gateway {
|
||||
* Public Restricted Functions *
|
||||
*******************************/
|
||||
|
||||
/// @inheritdoc IUSDCDestinationBridge
|
||||
function transferUSDCRoles(address _owner) external {
|
||||
require(msg.sender == circleCaller, "only circle caller");
|
||||
|
||||
_transferOwnership(_owner);
|
||||
}
|
||||
|
||||
/// @notice Update the Circle EOA address.
|
||||
/// @param _caller The address to update.
|
||||
function updateCircleCaller(address _caller) external onlyOwner {
|
||||
circleCaller = _caller;
|
||||
}
|
||||
|
||||
/// @notice Change the deposit pause status of this contract.
|
||||
/// @param _paused The new status, `true` means paused and `false` means not paused.
|
||||
function pauseDeposit(bool _paused) external onlyOwner {
|
||||
@@ -133,6 +150,7 @@ contract L2USDCGateway is L2ERC20Gateway {
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
require(_data.length == 0, "call is not allowed");
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
|
||||
156
contracts/src/L2/gateways/usdc/L2USDCGatewayCCTP.sol
Normal file
156
contracts/src/L2/gateways/usdc/L2USDCGatewayCCTP.sol
Normal file
@@ -0,0 +1,156 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity =0.8.16;
|
||||
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
|
||||
import {ITokenMessenger} from "../../../interfaces/ITokenMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "../../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../../IL2ScrollMessenger.sol";
|
||||
import {IL2ERC20Gateway} from "../IL2ERC20Gateway.sol";
|
||||
|
||||
import {CCTPGatewayBase} from "../../../libraries/gateway/CCTPGatewayBase.sol";
|
||||
import {ScrollGatewayBase} from "../../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {L2ERC20Gateway} from "../L2ERC20Gateway.sol";
|
||||
|
||||
/// @title L2USDCGatewayCCTP
|
||||
/// @notice The `L2USDCGatewayCCTP` contract is used to withdraw `USDC` token in layer 2 and
|
||||
/// finalize deposit `USDC` from layer 1.
|
||||
contract L2USDCGatewayCCTP is CCTPGatewayBase, L2ERC20Gateway {
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(
|
||||
address _l1USDC,
|
||||
address _l2USDC,
|
||||
uint32 _destinationDomain
|
||||
) CCTPGatewayBase(_l1USDC, _l2USDC, _destinationDomain) {}
|
||||
|
||||
/// @notice Initialize the storage of L2USDCGatewayCCTP.
|
||||
/// @param _counterpart The address of L1USDCGatewayCCTP in L1.
|
||||
/// @param _router The address of L2GatewayRouter.
|
||||
/// @param _messenger The address of L2ScrollMessenger.
|
||||
/// @param _cctpMessenger The address of TokenMessenger in local domain.
|
||||
/// @param _cctpTransmitter The address of MessageTransmitter in local domain.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger,
|
||||
address _cctpMessenger,
|
||||
address _cctpTransmitter
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
CCTPGatewayBase._initialize(_cctpMessenger, _cctpTransmitter);
|
||||
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address) external view override returns (address) {
|
||||
return l1USDC;
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address) public view override returns (address) {
|
||||
return l2USDC;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
/// @dev The function will not mint the USDC, users need to call `claimUSDC` after this function is done.
|
||||
function finalizeDepositERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
require(_l1Token == l1USDC, "l1 token not USDC");
|
||||
require(_l2Token == l2USDC, "l2 token not USDC");
|
||||
|
||||
uint256 _nonce;
|
||||
(_nonce, _data) = abi.decode(_data, (uint256, bytes));
|
||||
require(status[_nonce] == CCTPMessageStatus.None, "message relayed");
|
||||
status[_nonce] = CCTPMessageStatus.Pending;
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/*******************************
|
||||
* Public Restricted Functions *
|
||||
*******************************/
|
||||
|
||||
/// @notice Update the CCTP contract addresses.
|
||||
/// @param _messenger The address of TokenMessenger in local domain.
|
||||
/// @param _transmitter The address of MessageTransmitter in local domain.
|
||||
function updateCCTPContracts(address _messenger, address _transmitter) external onlyOwner {
|
||||
cctpMessenger = _messenger;
|
||||
cctpTransmitter = _transmitter;
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L2ERC20Gateway
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override {
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
require(_token == l2USDC, "only USDC is allowed");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
|
||||
// 3. Burn token through CCTP TokenMessenger
|
||||
uint256 _nonce = ITokenMessenger(cctpMessenger).depositForBurnWithCaller(
|
||||
_amount,
|
||||
destinationDomain,
|
||||
bytes32(uint256(uint160(_to))),
|
||||
address(this),
|
||||
bytes32(uint256(uint160(counterpart)))
|
||||
);
|
||||
|
||||
// 4. Generate message passed to L1USDCGateway.
|
||||
address _l1USDC = l1USDC;
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
_l1USDC,
|
||||
_token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
abi.encode(_nonce, _data)
|
||||
);
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit WithdrawERC20(_l1USDC, _token, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
A library for interacting with Scroll contracts.
|
||||
|
||||
This library includes contracts and interfaces needed to interact with the Scroll Smart Contracts deployed on both Layer 1 and Layer 2. This includes deposting and withdrawing ETH, ERC20 tokens and NFTs or sending arbitrary messages.
|
||||
This library includes contracts and interfaces needed to interact with the Scroll Smart Contracts deployed on both Layer 1 and Layer 2. This includes deposting and withdrawing ETH, ERC20 tokens and NFTs or sending arbitrary messages.
|
||||
|
||||
# Overview
|
||||
|
||||
@@ -21,10 +21,11 @@ pragma solidity 0.8.20;
|
||||
import "@scroll-tech/contracts/L1/gateways/IL1ETHGateway.sol";
|
||||
|
||||
contract MyContract {
|
||||
function bridgeETH(address scrollBridge, uint gasLimit) public payable {
|
||||
IL1ETHGateway(scrollBridge).depositETH(msg.sender, msg.value, gasLimit);
|
||||
}
|
||||
function bridgeETH(address scrollBridge, uint256 gasLimit) public payable {
|
||||
IL1ETHGateway(scrollBridge).depositETH(msg.sender, msg.value, gasLimit);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Visit the Bridge Documentation for API reference, architecture overview and guides with code examples.
|
||||
|
||||
16
contracts/src/interfaces/IMessageTransmitter.sol
Normal file
16
contracts/src/interfaces/IMessageTransmitter.sol
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.16;
|
||||
|
||||
interface IMessageTransmitter {
|
||||
function usedNonces(bytes32 _sourceAndNonce) external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @notice Receives an incoming message, validating the header and passing
|
||||
* the body to application-specific handler.
|
||||
* @param message The message raw bytes
|
||||
* @param signature The message signature
|
||||
* @return success bool, true if successful
|
||||
*/
|
||||
function receiveMessage(bytes calldata message, bytes calldata signature) external returns (bool success);
|
||||
}
|
||||
63
contracts/src/interfaces/ITokenMessenger.sol
Normal file
63
contracts/src/interfaces/ITokenMessenger.sol
Normal file
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.16;
|
||||
|
||||
interface ITokenMessenger {
|
||||
/**
|
||||
* @notice Deposits and burns tokens from sender to be minted on destination domain. The mint
|
||||
* on the destination domain must be called by `destinationCaller`.
|
||||
* WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible
|
||||
* to broadcast the message on the destination domain. This is an advanced feature, and the standard
|
||||
* depositForBurn() should be preferred for use cases where a specific destination caller is not required.
|
||||
* Emits a `DepositForBurn` event.
|
||||
* @dev reverts if:
|
||||
* - given destinationCaller is zero address
|
||||
* - given burnToken is not supported
|
||||
* - given destinationDomain has no TokenMessenger registered
|
||||
* - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
|
||||
* to this contract is less than `amount`.
|
||||
* - burn() reverts. For example, if `amount` is 0.
|
||||
* - MessageTransmitter returns false or reverts.
|
||||
* @param amount amount of tokens to burn
|
||||
* @param destinationDomain destination domain
|
||||
* @param mintRecipient address of mint recipient on destination domain
|
||||
* @param burnToken address of contract to burn deposited tokens, on local domain
|
||||
* @param destinationCaller caller on the destination domain, as bytes32
|
||||
* @return nonce unique nonce reserved by message
|
||||
*/
|
||||
function depositForBurnWithCaller(
|
||||
uint256 amount,
|
||||
uint32 destinationDomain,
|
||||
bytes32 mintRecipient,
|
||||
address burnToken,
|
||||
bytes32 destinationCaller
|
||||
) external returns (uint64 nonce);
|
||||
|
||||
/**
|
||||
* @notice Replace a BurnMessage to change the mint recipient and/or
|
||||
* destination caller. Allows the sender of a previous BurnMessage
|
||||
* (created by depositForBurn or depositForBurnWithCaller)
|
||||
* to send a new BurnMessage to replace the original.
|
||||
* The new BurnMessage will reuse the amount and burn token of the original,
|
||||
* without requiring a new deposit.
|
||||
* @dev The new message will reuse the original message's nonce. For a
|
||||
* given nonce, all replacement message(s) and the original message are
|
||||
* valid to broadcast on the destination domain, until the first message
|
||||
* at the nonce confirms, at which point all others are invalidated.
|
||||
* Note: The msg.sender of the replaced message must be the same as the
|
||||
* msg.sender of the original message.
|
||||
* @param originalMessage original message bytes (to replace)
|
||||
* @param originalAttestation original attestation bytes
|
||||
* @param newDestinationCaller the new destination caller, which may be the
|
||||
* same as the original destination caller, a new destination caller, or an empty
|
||||
* destination caller (bytes32(0), indicating that any destination caller is valid.)
|
||||
* @param newMintRecipient the new mint recipient, which may be the same as the
|
||||
* original mint recipient, or different.
|
||||
*/
|
||||
function replaceDepositForBurn(
|
||||
bytes calldata originalMessage,
|
||||
bytes calldata originalAttestation,
|
||||
bytes32 newDestinationCaller,
|
||||
bytes32 newMintRecipient
|
||||
) external;
|
||||
}
|
||||
12
contracts/src/interfaces/IUSDCBurnableSourceBridge.sol
Normal file
12
contracts/src/interfaces/IUSDCBurnableSourceBridge.sol
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.16;
|
||||
|
||||
// Implement this on the source chain (Ethereum).
|
||||
interface IUSDCBurnableSourceBridge {
|
||||
/**
|
||||
* @notice Called by Circle, this executes a burn on the source
|
||||
* chain.
|
||||
*/
|
||||
function burnAllLockedUSDC() external;
|
||||
}
|
||||
11
contracts/src/interfaces/IUSDCDestinationBridge.sol
Normal file
11
contracts/src/interfaces/IUSDCDestinationBridge.sol
Normal file
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.16;
|
||||
|
||||
// Implement this on the destination chain (Scroll).
|
||||
interface IUSDCDestinationBridge {
|
||||
/**
|
||||
* @notice Called by Circle, this transfers FiatToken roles to the designated owner.
|
||||
*/
|
||||
function transferUSDCRoles(address owner) external;
|
||||
}
|
||||
95
contracts/src/libraries/gateway/CCTPGatewayBase.sol
Normal file
95
contracts/src/libraries/gateway/CCTPGatewayBase.sol
Normal file
@@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {IMessageTransmitter} from "../../interfaces/IMessageTransmitter.sol";
|
||||
|
||||
import {ScrollGatewayBase} from "./ScrollGatewayBase.sol";
|
||||
|
||||
abstract contract CCTPGatewayBase is ScrollGatewayBase {
|
||||
/*********
|
||||
* Enums *
|
||||
*********/
|
||||
|
||||
enum CCTPMessageStatus {
|
||||
None,
|
||||
Pending,
|
||||
Relayed
|
||||
}
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L1 USDC address.
|
||||
address public immutable l1USDC;
|
||||
|
||||
/// @notice The address of L2 USDC address.
|
||||
address public immutable l2USDC;
|
||||
|
||||
/// @notice The destination domain for layer2.
|
||||
uint32 public immutable destinationDomain;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of TokenMessenger in local domain.
|
||||
address public cctpMessenger;
|
||||
|
||||
/// @notice The address of MessageTransmitter in local domain.
|
||||
address public cctpTransmitter;
|
||||
|
||||
/// @notice Mapping from destination domain CCTP nonce to status.
|
||||
mapping(uint256 => CCTPMessageStatus) public status;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(
|
||||
address _l1USDC,
|
||||
address _l2USDC,
|
||||
uint32 _destinationDomain
|
||||
) {
|
||||
l1USDC = _l1USDC;
|
||||
l2USDC = _l2USDC;
|
||||
destinationDomain = _destinationDomain;
|
||||
}
|
||||
|
||||
function _initialize(address _cctpMessenger, address _cctpTransmitter) internal {
|
||||
cctpMessenger = _cctpMessenger;
|
||||
cctpTransmitter = _cctpTransmitter;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Claim USDC that has been cross chained.
|
||||
/// @param _nonce The nonce of the message from CCTP.
|
||||
/// @param _cctpMessage The message passed to MessageTransmitter contract in CCTP.
|
||||
/// @param _cctpSignature The message passed to MessageTransmitter contract in CCTP.
|
||||
function claimUSDC(
|
||||
uint256 _nonce,
|
||||
bytes calldata _cctpMessage,
|
||||
bytes calldata _cctpSignature
|
||||
) public {
|
||||
// Check `_nonce` match with `_cctpMessage`.
|
||||
// According to the encoding of `_cctpMessage`, the nonce is in bytes 12 to 16.
|
||||
// See here: https://github.com/circlefin/evm-cctp-contracts/blob/master/src/messages/Message.sol#L29
|
||||
uint256 _expectedMessageNonce;
|
||||
assembly {
|
||||
_expectedMessageNonce := and(shr(96, calldataload(_cctpMessage.offset)), 0xffffffffffffffff)
|
||||
}
|
||||
require(_expectedMessageNonce == _nonce, "nonce mismatch");
|
||||
|
||||
require(status[_nonce] == CCTPMessageStatus.Pending, "message not relayed");
|
||||
|
||||
// call transmitter to mint USDC
|
||||
bool _success = IMessageTransmitter(cctpTransmitter).receiveMessage(_cctpMessage, _cctpSignature);
|
||||
require(_success, "call transmitter failed");
|
||||
|
||||
status[_nonce] = CCTPMessageStatus.Relayed;
|
||||
}
|
||||
}
|
||||
521
contracts/src/test/L1USDCGateway.t.sol
Normal file
521
contracts/src/test/L1USDCGateway.t.sol
Normal file
@@ -0,0 +1,521 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol";
|
||||
|
||||
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
|
||||
import {L1GatewayRouter} from "../L1/gateways/L1GatewayRouter.sol";
|
||||
import {IL1ERC20Gateway, L1USDCGateway} from "../L1/gateways/usdc/L1USDCGateway.sol";
|
||||
import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol";
|
||||
import {IL2ERC20Gateway, L2USDCGateway} from "../L2/gateways/usdc/L2USDCGateway.sol";
|
||||
import {AddressAliasHelper} from "../libraries/common/AddressAliasHelper.sol";
|
||||
|
||||
import {L1GatewayTestBase} from "./L1GatewayTestBase.t.sol";
|
||||
import {MockScrollMessenger} from "./mocks/MockScrollMessenger.sol";
|
||||
import {MockGatewayRecipient} from "./mocks/MockGatewayRecipient.sol";
|
||||
|
||||
contract L1USDCGatewayTest is L1GatewayTestBase {
|
||||
// from L1USDCGateway
|
||||
event FinalizeWithdrawERC20(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes _data
|
||||
);
|
||||
event DepositERC20(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes _data
|
||||
);
|
||||
|
||||
MockERC20 private l1USDC;
|
||||
MockERC20 private l2USDC;
|
||||
|
||||
L1USDCGateway private gateway;
|
||||
L1GatewayRouter private router;
|
||||
|
||||
L2USDCGateway private counterpartGateway;
|
||||
|
||||
function setUp() public {
|
||||
setUpBase();
|
||||
|
||||
// Deploy tokens
|
||||
l1USDC = new MockERC20("USDC", "USDC", 6);
|
||||
l2USDC = new MockERC20("USDC", "USDC", 6);
|
||||
|
||||
// Deploy L1 contracts
|
||||
gateway = _deployGateway();
|
||||
router = L1GatewayRouter(address(new ERC1967Proxy(address(new L1GatewayRouter()), new bytes(0))));
|
||||
|
||||
// Deploy L2 contracts
|
||||
counterpartGateway = new L2USDCGateway(address(l1USDC), address(l2USDC));
|
||||
|
||||
// Initialize L1 contracts
|
||||
gateway.initialize(address(counterpartGateway), address(router), address(l1Messenger));
|
||||
router.initialize(address(0), address(gateway));
|
||||
|
||||
// Prepare token balances
|
||||
l1USDC.mint(address(this), type(uint128).max);
|
||||
l1USDC.approve(address(gateway), type(uint256).max);
|
||||
l1USDC.approve(address(router), type(uint256).max);
|
||||
}
|
||||
|
||||
function testInitialized() public {
|
||||
assertEq(address(counterpartGateway), gateway.counterpart());
|
||||
assertEq(address(router), gateway.router());
|
||||
assertEq(address(l1Messenger), gateway.messenger());
|
||||
assertEq(address(l1USDC), gateway.l1USDC());
|
||||
assertEq(address(l2USDC), gateway.l2USDC());
|
||||
assertEq(address(l2USDC), gateway.getL2ERC20Address(address(l1USDC)));
|
||||
|
||||
hevm.expectRevert("Initializable: contract is already initialized");
|
||||
gateway.initialize(address(counterpartGateway), address(router), address(l1Messenger));
|
||||
}
|
||||
|
||||
function testDepositPaused() public {
|
||||
// non-owner call pause, should revert
|
||||
hevm.startPrank(address(1));
|
||||
hevm.expectRevert("Ownable: caller is not the owner");
|
||||
gateway.pauseDeposit(false);
|
||||
hevm.expectRevert("Ownable: caller is not the owner");
|
||||
gateway.pauseDeposit(true);
|
||||
hevm.stopPrank();
|
||||
|
||||
// pause deposit
|
||||
gateway.pauseDeposit(true);
|
||||
|
||||
// deposit paused, should revert
|
||||
hevm.expectRevert("deposit paused");
|
||||
gateway.depositERC20(address(l1USDC), 1, 0);
|
||||
hevm.expectRevert("deposit paused");
|
||||
gateway.depositERC20(address(l1USDC), address(this), 1, 0);
|
||||
hevm.expectRevert("deposit paused");
|
||||
gateway.depositERC20AndCall(address(l1USDC), address(this), 1, new bytes(0), 0);
|
||||
}
|
||||
|
||||
function testPauseWithdraw() public {
|
||||
// non-owner call pause, should revert
|
||||
hevm.startPrank(address(1));
|
||||
hevm.expectRevert("Ownable: caller is not the owner");
|
||||
gateway.pauseWithdraw(false);
|
||||
hevm.expectRevert("Ownable: caller is not the owner");
|
||||
gateway.pauseWithdraw(true);
|
||||
hevm.stopPrank();
|
||||
}
|
||||
|
||||
function testDepositERC20(
|
||||
uint256 amount,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) public {
|
||||
_depositERC20(false, amount, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testDepositERC20WithRecipient(
|
||||
uint256 amount,
|
||||
address recipient,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) public {
|
||||
_depositERC20WithRecipient(false, amount, recipient, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testRouterDepositERC20(
|
||||
uint256 amount,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) public {
|
||||
_depositERC20(true, amount, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testRouterDepositERC20WithRecipient(
|
||||
uint256 amount,
|
||||
address recipient,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) public {
|
||||
_depositERC20WithRecipient(true, amount, recipient, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testFinalizeWithdrawERC20FailedMocking(
|
||||
address sender,
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
bytes memory dataToCall
|
||||
) public {
|
||||
amount = bound(amount, 1, 100000);
|
||||
|
||||
// revert when caller is not messenger
|
||||
hevm.expectRevert("only messenger can call");
|
||||
gateway.finalizeWithdrawERC20(address(l1USDC), address(l2USDC), sender, recipient, amount, dataToCall);
|
||||
|
||||
MockScrollMessenger mockMessenger = new MockScrollMessenger();
|
||||
gateway = _deployGateway();
|
||||
gateway.initialize(address(counterpartGateway), address(router), address(mockMessenger));
|
||||
|
||||
// only call by conterpart
|
||||
hevm.expectRevert("only call by counterpart");
|
||||
mockMessenger.callTarget(
|
||||
address(gateway),
|
||||
abi.encodeWithSelector(
|
||||
gateway.finalizeWithdrawERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
sender,
|
||||
recipient,
|
||||
amount,
|
||||
dataToCall
|
||||
)
|
||||
);
|
||||
|
||||
mockMessenger.setXDomainMessageSender(address(counterpartGateway));
|
||||
|
||||
// nonzero msg.value
|
||||
hevm.expectRevert("nonzero msg.value");
|
||||
mockMessenger.callTarget{value: 1}(
|
||||
address(gateway),
|
||||
abi.encodeWithSelector(
|
||||
gateway.finalizeWithdrawERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
sender,
|
||||
recipient,
|
||||
amount,
|
||||
dataToCall
|
||||
)
|
||||
);
|
||||
|
||||
// l1 token not USDC
|
||||
hevm.expectRevert("l1 token not USDC");
|
||||
mockMessenger.callTarget(
|
||||
address(gateway),
|
||||
abi.encodeWithSelector(
|
||||
gateway.finalizeWithdrawERC20.selector,
|
||||
address(l2USDC),
|
||||
address(l2USDC),
|
||||
sender,
|
||||
recipient,
|
||||
amount,
|
||||
dataToCall
|
||||
)
|
||||
);
|
||||
|
||||
// l2 token not USDC
|
||||
hevm.expectRevert("l2 token not USDC");
|
||||
mockMessenger.callTarget(
|
||||
address(gateway),
|
||||
abi.encodeWithSelector(
|
||||
gateway.finalizeWithdrawERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l1USDC),
|
||||
sender,
|
||||
recipient,
|
||||
amount,
|
||||
dataToCall
|
||||
)
|
||||
);
|
||||
|
||||
// withdraw paused
|
||||
gateway.pauseWithdraw(true);
|
||||
hevm.expectRevert("withdraw paused");
|
||||
mockMessenger.callTarget(
|
||||
address(gateway),
|
||||
abi.encodeWithSelector(
|
||||
gateway.finalizeWithdrawERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
sender,
|
||||
recipient,
|
||||
amount,
|
||||
dataToCall
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function testFinalizeWithdrawERC20Failed(
|
||||
address sender,
|
||||
address recipient,
|
||||
uint256 amount,
|
||||
bytes memory dataToCall
|
||||
) public {
|
||||
// blacklist some addresses
|
||||
hevm.assume(recipient != address(0));
|
||||
hevm.assume(recipient != address(gateway));
|
||||
|
||||
amount = bound(amount, 1, l1USDC.balanceOf(address(this)));
|
||||
|
||||
// deposit some USDC to L1ScrollMessenger
|
||||
gateway.depositERC20(address(l1USDC), amount, 0);
|
||||
|
||||
// do finalize withdraw usdc
|
||||
bytes memory message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
sender,
|
||||
recipient,
|
||||
amount,
|
||||
dataToCall
|
||||
);
|
||||
bytes memory xDomainCalldata = abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
address(uint160(address(counterpartGateway)) + 1),
|
||||
address(gateway),
|
||||
0,
|
||||
0,
|
||||
message
|
||||
);
|
||||
|
||||
prepareL2MessageRoot(keccak256(xDomainCalldata));
|
||||
|
||||
IL1ScrollMessenger.L2MessageProof memory proof;
|
||||
proof.batchIndex = rollup.lastFinalizedBatchIndex();
|
||||
|
||||
// conterpart is not L2USDCGateway
|
||||
// emit FailedRelayedMessage from L1ScrollMessenger
|
||||
hevm.expectEmit(true, false, false, true);
|
||||
emit FailedRelayedMessage(keccak256(xDomainCalldata));
|
||||
|
||||
uint256 gatewayBalance = l1USDC.balanceOf(address(gateway));
|
||||
uint256 recipientBalance = l1USDC.balanceOf(recipient);
|
||||
assertBoolEq(false, l1Messenger.isL2MessageExecuted(keccak256(xDomainCalldata)));
|
||||
l1Messenger.relayMessageWithProof(
|
||||
address(uint160(address(counterpartGateway)) + 1),
|
||||
address(gateway),
|
||||
0,
|
||||
0,
|
||||
message,
|
||||
proof
|
||||
);
|
||||
assertEq(gatewayBalance, l1USDC.balanceOf(address(gateway)));
|
||||
assertEq(recipientBalance, l1USDC.balanceOf(recipient));
|
||||
assertBoolEq(false, l1Messenger.isL2MessageExecuted(keccak256(xDomainCalldata)));
|
||||
}
|
||||
|
||||
function testFinalizeWithdrawERC20(
|
||||
address sender,
|
||||
uint256 amount,
|
||||
bytes memory dataToCall
|
||||
) public {
|
||||
MockGatewayRecipient recipient = new MockGatewayRecipient();
|
||||
|
||||
amount = bound(amount, 1, l1USDC.balanceOf(address(this)));
|
||||
|
||||
// deposit some USDC to gateway
|
||||
gateway.depositERC20(address(l1USDC), amount, 0);
|
||||
|
||||
// do finalize withdraw usdc
|
||||
bytes memory message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
sender,
|
||||
address(recipient),
|
||||
amount,
|
||||
dataToCall
|
||||
);
|
||||
bytes memory xDomainCalldata = abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
address(counterpartGateway),
|
||||
address(gateway),
|
||||
0,
|
||||
0,
|
||||
message
|
||||
);
|
||||
|
||||
prepareL2MessageRoot(keccak256(xDomainCalldata));
|
||||
|
||||
IL1ScrollMessenger.L2MessageProof memory proof;
|
||||
proof.batchIndex = rollup.lastFinalizedBatchIndex();
|
||||
|
||||
// emit FinalizeWithdrawERC20 from L1USDCGateway
|
||||
{
|
||||
hevm.expectEmit(true, true, true, true);
|
||||
emit FinalizeWithdrawERC20(
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
sender,
|
||||
address(recipient),
|
||||
amount,
|
||||
dataToCall
|
||||
);
|
||||
}
|
||||
|
||||
// emit RelayedMessage from L1ScrollMessenger
|
||||
{
|
||||
hevm.expectEmit(true, false, false, true);
|
||||
emit RelayedMessage(keccak256(xDomainCalldata));
|
||||
}
|
||||
|
||||
uint256 gatewayBalance = l1USDC.balanceOf(address(gateway));
|
||||
uint256 recipientBalance = l1USDC.balanceOf(address(recipient));
|
||||
assertBoolEq(false, l1Messenger.isL2MessageExecuted(keccak256(xDomainCalldata)));
|
||||
l1Messenger.relayMessageWithProof(address(counterpartGateway), address(gateway), 0, 0, message, proof);
|
||||
assertEq(gatewayBalance - amount, l1USDC.balanceOf(address(gateway)));
|
||||
assertEq(recipientBalance + amount, l1USDC.balanceOf(address(recipient)));
|
||||
assertBoolEq(true, l1Messenger.isL2MessageExecuted(keccak256(xDomainCalldata)));
|
||||
}
|
||||
|
||||
function _depositERC20(
|
||||
bool useRouter,
|
||||
uint256 amount,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) private {
|
||||
amount = bound(amount, 0, l1USDC.balanceOf(address(this)));
|
||||
gasLimit = bound(gasLimit, 0, 1000000);
|
||||
feePerGas = bound(feePerGas, 0, 1000);
|
||||
|
||||
gasOracle.setL2BaseFee(feePerGas);
|
||||
|
||||
uint256 feeToPay = feePerGas * gasLimit;
|
||||
bytes memory message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
address(this),
|
||||
address(this),
|
||||
amount,
|
||||
new bytes(0)
|
||||
);
|
||||
bytes memory xDomainCalldata = abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
address(gateway),
|
||||
address(counterpartGateway),
|
||||
0,
|
||||
0,
|
||||
message
|
||||
);
|
||||
|
||||
if (amount == 0) {
|
||||
hevm.expectRevert("deposit zero amount");
|
||||
if (useRouter) {
|
||||
router.depositERC20{value: feeToPay + extraValue}(address(l1USDC), amount, gasLimit);
|
||||
} else {
|
||||
gateway.depositERC20{value: feeToPay + extraValue}(address(l1USDC), amount, gasLimit);
|
||||
}
|
||||
} else {
|
||||
// token is not l1USDC
|
||||
hevm.expectRevert("only USDC is allowed");
|
||||
gateway.depositERC20(address(l2USDC), amount, gasLimit);
|
||||
|
||||
// emit QueueTransaction from L1MessageQueue
|
||||
{
|
||||
hevm.expectEmit(true, true, false, true);
|
||||
address sender = AddressAliasHelper.applyL1ToL2Alias(address(l1Messenger));
|
||||
emit QueueTransaction(sender, address(l2Messenger), 0, 0, gasLimit, xDomainCalldata);
|
||||
}
|
||||
|
||||
// emit SentMessage from L1ScrollMessenger
|
||||
{
|
||||
hevm.expectEmit(true, true, false, true);
|
||||
emit SentMessage(address(gateway), address(counterpartGateway), 0, 0, gasLimit, message);
|
||||
}
|
||||
|
||||
// emit DepositERC20 from L1USDCGateway
|
||||
hevm.expectEmit(true, true, true, true);
|
||||
emit DepositERC20(address(l1USDC), address(l2USDC), address(this), address(this), amount, new bytes(0));
|
||||
|
||||
uint256 gatewayBalance = l1USDC.balanceOf(address(gateway));
|
||||
uint256 feeVaultBalance = address(feeVault).balance;
|
||||
assertBoolEq(false, l1Messenger.isL1MessageSent(keccak256(xDomainCalldata)));
|
||||
if (useRouter) {
|
||||
router.depositERC20{value: feeToPay + extraValue}(address(l1USDC), amount, gasLimit);
|
||||
} else {
|
||||
gateway.depositERC20{value: feeToPay + extraValue}(address(l1USDC), amount, gasLimit);
|
||||
}
|
||||
assertEq(amount + gatewayBalance, l1USDC.balanceOf(address(gateway)));
|
||||
assertEq(feeToPay + feeVaultBalance, address(feeVault).balance);
|
||||
assertBoolEq(true, l1Messenger.isL1MessageSent(keccak256(xDomainCalldata)));
|
||||
}
|
||||
}
|
||||
|
||||
function _depositERC20WithRecipient(
|
||||
bool useRouter,
|
||||
uint256 amount,
|
||||
address recipient,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) private {
|
||||
amount = bound(amount, 0, l1USDC.balanceOf(address(this)));
|
||||
gasLimit = bound(gasLimit, 0, 1000000);
|
||||
feePerGas = bound(feePerGas, 0, 1000);
|
||||
|
||||
gasOracle.setL2BaseFee(feePerGas);
|
||||
|
||||
uint256 feeToPay = feePerGas * gasLimit;
|
||||
bytes memory message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
address(this),
|
||||
recipient,
|
||||
amount,
|
||||
new bytes(0)
|
||||
);
|
||||
bytes memory xDomainCalldata = abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
address(gateway),
|
||||
address(counterpartGateway),
|
||||
0,
|
||||
0,
|
||||
message
|
||||
);
|
||||
|
||||
if (amount == 0) {
|
||||
hevm.expectRevert("deposit zero amount");
|
||||
if (useRouter) {
|
||||
router.depositERC20{value: feeToPay + extraValue}(address(l1USDC), recipient, amount, gasLimit);
|
||||
} else {
|
||||
gateway.depositERC20{value: feeToPay + extraValue}(address(l1USDC), recipient, amount, gasLimit);
|
||||
}
|
||||
} else {
|
||||
// token is not l1USDC
|
||||
hevm.expectRevert("only USDC is allowed");
|
||||
gateway.depositERC20(address(l2USDC), recipient, amount, gasLimit);
|
||||
|
||||
// emit QueueTransaction from L1MessageQueue
|
||||
{
|
||||
hevm.expectEmit(true, true, false, true);
|
||||
address sender = AddressAliasHelper.applyL1ToL2Alias(address(l1Messenger));
|
||||
emit QueueTransaction(sender, address(l2Messenger), 0, 0, gasLimit, xDomainCalldata);
|
||||
}
|
||||
|
||||
// emit SentMessage from L1ScrollMessenger
|
||||
{
|
||||
hevm.expectEmit(true, true, false, true);
|
||||
emit SentMessage(address(gateway), address(counterpartGateway), 0, 0, gasLimit, message);
|
||||
}
|
||||
|
||||
// emit DepositERC20 from L1USDCGateway
|
||||
hevm.expectEmit(true, true, true, true);
|
||||
emit DepositERC20(address(l1USDC), address(l2USDC), address(this), recipient, amount, new bytes(0));
|
||||
|
||||
uint256 gatewayBalance = l1USDC.balanceOf(address(gateway));
|
||||
uint256 feeVaultBalance = address(feeVault).balance;
|
||||
assertBoolEq(false, l1Messenger.isL1MessageSent(keccak256(xDomainCalldata)));
|
||||
if (useRouter) {
|
||||
router.depositERC20{value: feeToPay + extraValue}(address(l1USDC), recipient, amount, gasLimit);
|
||||
} else {
|
||||
gateway.depositERC20{value: feeToPay + extraValue}(address(l1USDC), recipient, amount, gasLimit);
|
||||
}
|
||||
assertEq(amount + gatewayBalance, l1USDC.balanceOf(address(gateway)));
|
||||
assertEq(feeToPay + feeVaultBalance, address(feeVault).balance);
|
||||
assertBoolEq(true, l1Messenger.isL1MessageSent(keccak256(xDomainCalldata)));
|
||||
}
|
||||
}
|
||||
|
||||
function _deployGateway() internal returns (L1USDCGateway) {
|
||||
return
|
||||
L1USDCGateway(
|
||||
payable(new ERC1967Proxy(address(new L1USDCGateway(address(l1USDC), address(l2USDC))), new bytes(0)))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ contract L2USDCGatewayTest is L2GatewayTestBase {
|
||||
router = L2GatewayRouter(address(new ERC1967Proxy(address(new L2GatewayRouter()), new bytes(0))));
|
||||
|
||||
// Deploy L1 contracts
|
||||
counterpartGateway = new L1USDCGateway();
|
||||
counterpartGateway = new L1USDCGateway(address(l1USDC), address(l2USDC));
|
||||
|
||||
// Initialize L2 contracts
|
||||
gateway.initialize(address(counterpartGateway), address(router), address(l2Messenger));
|
||||
@@ -128,16 +128,6 @@ contract L2USDCGatewayTest is L2GatewayTestBase {
|
||||
_withdrawERC20WithRecipient(false, amount, recipient, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testWithdrawERC20WithRecipientAndCalldata(
|
||||
uint256 amount,
|
||||
address recipient,
|
||||
bytes memory dataToCall,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) public {
|
||||
_withdrawERC20WithRecipientAndCalldata(false, amount, recipient, dataToCall, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testRouterWithdrawERC20(
|
||||
uint256 amount,
|
||||
uint256 gasLimit,
|
||||
@@ -155,16 +145,6 @@ contract L2USDCGatewayTest is L2GatewayTestBase {
|
||||
_withdrawERC20WithRecipient(true, amount, recipient, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testRouterWithdrawERC20WithRecipientAndCalldata(
|
||||
uint256 amount,
|
||||
address recipient,
|
||||
bytes memory dataToCall,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) public {
|
||||
_withdrawERC20WithRecipientAndCalldata(true, amount, recipient, dataToCall, gasLimit, feePerGas);
|
||||
}
|
||||
|
||||
function testFinalizeDepositERC20FailedMocking(
|
||||
address sender,
|
||||
address recipient,
|
||||
@@ -356,7 +336,6 @@ contract L2USDCGatewayTest is L2GatewayTestBase {
|
||||
) private {
|
||||
amount = bound(amount, 0, l2USDC.balanceOf(address(this)));
|
||||
gasLimit = bound(gasLimit, 21000, 1000000);
|
||||
feePerGas = bound(feePerGas, 0, 1000);
|
||||
feePerGas = 0;
|
||||
|
||||
setL1BaseFee(feePerGas);
|
||||
@@ -433,7 +412,6 @@ contract L2USDCGatewayTest is L2GatewayTestBase {
|
||||
) private {
|
||||
amount = bound(amount, 0, l2USDC.balanceOf(address(this)));
|
||||
gasLimit = bound(gasLimit, 21000, 1000000);
|
||||
feePerGas = bound(feePerGas, 0, 1000);
|
||||
feePerGas = 0;
|
||||
|
||||
setL1BaseFee(feePerGas);
|
||||
@@ -501,85 +479,6 @@ contract L2USDCGatewayTest is L2GatewayTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
function _withdrawERC20WithRecipientAndCalldata(
|
||||
bool useRouter,
|
||||
uint256 amount,
|
||||
address recipient,
|
||||
bytes memory dataToCall,
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) private {
|
||||
amount = bound(amount, 0, l2USDC.balanceOf(address(this)));
|
||||
gasLimit = bound(gasLimit, 21000, 1000000);
|
||||
feePerGas = bound(feePerGas, 0, 1000);
|
||||
// we don't charge fee now.
|
||||
feePerGas = 0;
|
||||
|
||||
setL1BaseFee(feePerGas);
|
||||
|
||||
uint256 feeToPay = feePerGas * gasLimit;
|
||||
bytes memory message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
address(l1USDC),
|
||||
address(l2USDC),
|
||||
address(this),
|
||||
recipient,
|
||||
amount,
|
||||
dataToCall
|
||||
);
|
||||
bytes memory xDomainCalldata = abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
address(gateway),
|
||||
address(counterpartGateway),
|
||||
0,
|
||||
0,
|
||||
message
|
||||
);
|
||||
|
||||
if (amount == 0) {
|
||||
hevm.expectRevert("withdraw zero amount");
|
||||
if (useRouter) {
|
||||
router.withdrawERC20AndCall{value: feeToPay}(address(l2USDC), recipient, amount, dataToCall, gasLimit);
|
||||
} else {
|
||||
gateway.withdrawERC20AndCall{value: feeToPay}(address(l2USDC), recipient, amount, dataToCall, gasLimit);
|
||||
}
|
||||
} else {
|
||||
// token is not l1USDC
|
||||
hevm.expectRevert("only USDC is allowed");
|
||||
gateway.withdrawERC20AndCall(address(l1USDC), recipient, amount, dataToCall, gasLimit);
|
||||
|
||||
// emit AppendMessage from L2MessageQueue
|
||||
{
|
||||
hevm.expectEmit(false, false, false, true);
|
||||
emit AppendMessage(0, keccak256(xDomainCalldata));
|
||||
}
|
||||
|
||||
// emit SentMessage from L2ScrollMessenger
|
||||
{
|
||||
hevm.expectEmit(true, true, false, true);
|
||||
emit SentMessage(address(gateway), address(counterpartGateway), 0, 0, gasLimit, message);
|
||||
}
|
||||
|
||||
// emit WithdrawERC20 from L2USDCGateway
|
||||
hevm.expectEmit(true, true, true, true);
|
||||
emit WithdrawERC20(address(l1USDC), address(l2USDC), address(this), recipient, amount, dataToCall);
|
||||
|
||||
uint256 senderBalance = l2USDC.balanceOf(address(this));
|
||||
uint256 gatewayBalance = l2USDC.balanceOf(address(gateway));
|
||||
uint256 feeVaultBalance = address(feeVault).balance;
|
||||
assertBoolEq(false, l2Messenger.isL2MessageSent(keccak256(xDomainCalldata)));
|
||||
if (useRouter) {
|
||||
router.withdrawERC20AndCall{value: feeToPay}(address(l2USDC), recipient, amount, dataToCall, gasLimit);
|
||||
} else {
|
||||
gateway.withdrawERC20AndCall{value: feeToPay}(address(l2USDC), recipient, amount, dataToCall, gasLimit);
|
||||
}
|
||||
assertEq(senderBalance - amount, l2USDC.balanceOf(address(this)));
|
||||
assertEq(gatewayBalance, l2USDC.balanceOf(address(gateway)));
|
||||
assertEq(feeToPay + feeVaultBalance, address(feeVault).balance);
|
||||
assertBoolEq(true, l2Messenger.isL2MessageSent(keccak256(xDomainCalldata)));
|
||||
}
|
||||
}
|
||||
|
||||
function _deployGateway() internal returns (L2USDCGateway) {
|
||||
return
|
||||
L2USDCGateway(
|
||||
|
||||
@@ -410,7 +410,7 @@ contract L2WETHGatewayTest is L2GatewayTestBase {
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) private {
|
||||
amount = bound(amount, 0, l1weth.balanceOf(address(this)));
|
||||
amount = bound(amount, 0, l2weth.balanceOf(address(this)));
|
||||
gasLimit = bound(gasLimit, 21000, 1000000);
|
||||
feePerGas = 0;
|
||||
|
||||
@@ -485,7 +485,7 @@ contract L2WETHGatewayTest is L2GatewayTestBase {
|
||||
uint256 gasLimit,
|
||||
uint256 feePerGas
|
||||
) private {
|
||||
amount = bound(amount, 0, l1weth.balanceOf(address(this)));
|
||||
amount = bound(amount, 0, l2weth.balanceOf(address(this)));
|
||||
gasLimit = bound(gasLimit, 21000, 1000000);
|
||||
feePerGas = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user