Skip to content

Commit ce4025b

Browse files
authored
feat(contracts): lido gateway (#988)
1 parent e3fd42e commit ce4025b

18 files changed

+2691
-1
lines changed

.solcover.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
skipFiles: [
3+
'mocks',
4+
'test',
5+
'L2/predeploys/L1BlockContainer.sol',
6+
'libraries/verifier/ZkTrieVerifier.sol',
7+
'libraries/verifier/PatriciaMerkleTrieVerifier.sol'
8+
],
9+
istanbulReporter: ["lcov", "json"]
10+
};

foundry.toml

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ sender = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # the address of `
2020
tx_origin = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # the address of `tx.origin` in tests
2121
initial_balance = '0xffffffffffffffffffffffff' # the initial balance of the test contract
2222
block_number = 0 # the block number we are at in tests
23-
chain_id = 99 # the chain id we are on in tests
2423
gas_limit = 9223372036854775807 # the gas limit in tests
2524
gas_price = 0 # the gas price (in wei) in tests
2625
block_base_fee_per_gas = 0 # the base fee (in wei) in tests

integration-test/GasOptimizationUpgrade.spec.ts

+79
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,46 @@ describe("GasOptimizationUpgrade.spec", async () => {
334334
"L1GatewayRouter.depositERC20 USDC after upgrade"
335335
);
336336
});
337+
338+
it.skip("should succeed on L1LidoGateway", async () => {
339+
const L1_WSTETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0";
340+
const L2_WSTETH = "0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32";
341+
const L1_GATEWAY = "0x6625C6332c9F91F2D27c304E729B86db87A3f504";
342+
const L2_GATEWAY = "0x8aE8f22226B9d789A36AC81474e633f8bE2856c9";
343+
const L1LidoGateway = await ethers.getContractFactory("L1LidoGateway", deployer);
344+
const impl = await L1LidoGateway.deploy(L1_WSTETH, L2_WSTETH, L2_GATEWAY, L1_ROUTER, L1_MESSENGER);
345+
const gateway = await ethers.getContractAt("L1LidoGateway", L1_GATEWAY, deployer);
346+
const amountIn = ethers.utils.parseUnits("1", 6);
347+
const fee = await queue.estimateCrossDomainMessageFee(1e6);
348+
const token = await ethers.getContractAt("MockERC20", L1_WSTETH, deployer);
349+
await mockERC20Balance(token.address, amountIn.mul(10), 0);
350+
await token.approve(L1_GATEWAY, constants.MaxUint256);
351+
await token.approve(L1_ROUTER, constants.MaxUint256);
352+
353+
// before upgrade
354+
await showGasUsage(
355+
await gateway["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
356+
"L1LidoGateway.depositERC20 wstETH before upgrade"
357+
);
358+
await showGasUsage(
359+
await router["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
360+
"L1GatewayRouter.depositERC20 wstETH before upgrade"
361+
);
362+
363+
// do upgrade
364+
await upgradeL1(L1_GATEWAY, impl.address);
365+
await gateway.initializeV2(deployer.address, deployer.address, deployer.address, deployer.address);
366+
367+
// after upgrade
368+
await showGasUsage(
369+
await gateway["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
370+
"L1LidoGateway.depositERC20 wstETH after upgrade"
371+
);
372+
await showGasUsage(
373+
await router["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
374+
"L1GatewayRouter.depositERC20 wstETH after upgrade"
375+
);
376+
});
337377
});
338378

339379
context("L2 upgrade", async () => {
@@ -584,5 +624,44 @@ describe("GasOptimizationUpgrade.spec", async () => {
584624
"L2GatewayRouter.withdrawERC20 USDC after upgrade"
585625
);
586626
});
627+
628+
it.skip("should succeed on L2LidoGateway", async () => {
629+
const L1_WSTETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0";
630+
const L2_WSTETH = "0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32";
631+
const L1_GATEWAY = "0x6625C6332c9F91F2D27c304E729B86db87A3f504";
632+
const L2_GATEWAY = "0x8aE8f22226B9d789A36AC81474e633f8bE2856c9";
633+
const L2LidoGateway = await ethers.getContractFactory("L2LidoGateway", deployer);
634+
const impl = await L2LidoGateway.deploy(L1_WSTETH, L2_WSTETH, L1_GATEWAY, L2_ROUTER, L2_MESSENGER);
635+
const gateway = await ethers.getContractAt("L2LidoGateway", L2_GATEWAY, deployer);
636+
const amountIn = ethers.utils.parseUnits("1", 6);
637+
const token = await ethers.getContractAt("MockERC20", L2_WSTETH, deployer);
638+
await mockERC20Balance(token.address, amountIn.mul(10), 51);
639+
await token.approve(L2_GATEWAY, constants.MaxUint256);
640+
await token.approve(L2_ROUTER, constants.MaxUint256);
641+
642+
// before upgrade
643+
await showGasUsage(
644+
await gateway["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
645+
"L2LidoGateway.withdrawERC20 wstETH before upgrade"
646+
);
647+
await showGasUsage(
648+
await router["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
649+
"L2GatewayRouter.withdrawERC20 wstETH before upgrade"
650+
);
651+
652+
// do upgrade
653+
await upgradeL2(L2_GATEWAY, impl.address);
654+
await gateway.initializeV2(deployer.address, deployer.address, deployer.address, deployer.address);
655+
656+
// after upgrade
657+
await showGasUsage(
658+
await gateway["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
659+
"L2LidoGateway.withdrawERC20 wstETH after upgrade"
660+
);
661+
await showGasUsage(
662+
await router["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
663+
"L2GatewayRouter.withdrawERC20 wstETH after upgrade"
664+
);
665+
});
587666
});
588667
});
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.10;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {console} from "forge-std/console.sol";
6+
7+
import {L1LidoGateway} from "../../src/lido/L1LidoGateway.sol";
8+
import {L2LidoGateway} from "../../src/lido/L2LidoGateway.sol";
9+
10+
// solhint-disable state-visibility
11+
// solhint-disable var-name-mixedcase
12+
13+
contract DeployLidoGateway is Script {
14+
string NETWORK = vm.envString("NETWORK");
15+
16+
uint256 L1_DEPLOYER_PRIVATE_KEY = vm.envUint("L1_DEPLOYER_PRIVATE_KEY");
17+
18+
uint256 L2_DEPLOYER_PRIVATE_KEY = vm.envUint("L2_DEPLOYER_PRIVATE_KEY");
19+
20+
address L1_WSTETH_ADDR = vm.envAddress("L1_WSTETH_ADDR");
21+
22+
address L2_WSTETH_ADDR = vm.envAddress("L2_WSTETH_ADDR");
23+
24+
address L1_SCROLL_MESSENGER_PROXY_ADDR = vm.envAddress("L1_SCROLL_MESSENGER_PROXY_ADDR");
25+
address L1_GATEWAY_ROUTER_PROXY_ADDR = vm.envAddress("L1_GATEWAY_ROUTER_PROXY_ADDR");
26+
address L1_LIDO_GATEWAY_PROXY_ADDR = vm.envAddress("L1_LIDO_GATEWAY_PROXY_ADDR");
27+
28+
address L2_SCROLL_MESSENGER_PROXY_ADDR = vm.envAddress("L2_SCROLL_MESSENGER_PROXY_ADDR");
29+
address L2_GATEWAY_ROUTER_PROXY_ADDR = vm.envAddress("L2_GATEWAY_ROUTER_PROXY_ADDR");
30+
address L2_LIDO_GATEWAY_PROXY_ADDR = vm.envAddress("L2_LIDO_GATEWAY_PROXY_ADDR");
31+
32+
function run() external {
33+
vm.startBroadcast(L2_DEPLOYER_PRIVATE_KEY);
34+
35+
if (keccak256(abi.encodePacked(NETWORK)) == keccak256(abi.encodePacked("L1"))) {
36+
// deploy l1 lido gateway
37+
L1LidoGateway gateway = new L1LidoGateway(
38+
L1_WSTETH_ADDR,
39+
L2_WSTETH_ADDR,
40+
L2_LIDO_GATEWAY_PROXY_ADDR,
41+
L1_GATEWAY_ROUTER_PROXY_ADDR,
42+
L1_SCROLL_MESSENGER_PROXY_ADDR
43+
);
44+
logAddress("L1_LIDO_GATEWAY_IMPLEMENTATION_ADDR", address(gateway));
45+
} else if (keccak256(abi.encodePacked(NETWORK)) == keccak256(abi.encodePacked("L2"))) {
46+
// deploy l2 lido gateway
47+
L2LidoGateway gateway = new L2LidoGateway(
48+
L1_WSTETH_ADDR,
49+
L2_WSTETH_ADDR,
50+
L1_LIDO_GATEWAY_PROXY_ADDR,
51+
L2_GATEWAY_ROUTER_PROXY_ADDR,
52+
L2_SCROLL_MESSENGER_PROXY_ADDR
53+
);
54+
logAddress("L2_LIDO_GATEWAY_IMPLEMENTATION_ADDR", address(gateway));
55+
}
56+
57+
vm.stopBroadcast();
58+
}
59+
60+
function logAddress(string memory name, address addr) internal view {
61+
console.log(string(abi.encodePacked(name, "=", vm.toString(address(addr)))));
62+
}
63+
}

src/lido/L1LidoGateway.sol

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity =0.8.16;
4+
5+
import {IL1ERC20Gateway} from "../L1/gateways/IL1ERC20Gateway.sol";
6+
import {L1ERC20Gateway} from "../L1/gateways/L1ERC20Gateway.sol";
7+
import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol";
8+
import {IL2ERC20Gateway} from "../L2/gateways/IL2ERC20Gateway.sol";
9+
import {ScrollGatewayBase} from "../libraries/gateway/ScrollGatewayBase.sol";
10+
11+
import {LidoBridgeableTokens} from "./LidoBridgeableTokens.sol";
12+
import {LidoGatewayManager} from "./LidoGatewayManager.sol";
13+
14+
contract L1LidoGateway is L1ERC20Gateway, LidoBridgeableTokens, LidoGatewayManager {
15+
/**********
16+
* Errors *
17+
**********/
18+
19+
/// @dev Thrown when deposit zero amount token.
20+
error ErrorDepositZeroAmount();
21+
22+
/// @dev Thrown when deposit erc20 with calldata.
23+
error DepositAndCallIsNotAllowed();
24+
25+
/*************
26+
* Variables *
27+
*************/
28+
29+
/// @dev The initial version of `L1LidoGateway` use `L1CustomERC20Gateway`. We keep the storage
30+
/// slot for `tokenMapping` for compatibility. It should no longer be used.
31+
mapping(address => address) private __tokenMapping;
32+
33+
/***************
34+
* Constructor *
35+
***************/
36+
37+
/// @notice Constructor for `L1LidoGateway` implementation contract.
38+
///
39+
/// @param _l1Token The address of the bridged token in the L1 chain
40+
/// @param _l2Token The address of the token minted on the L2 chain when token bridged
41+
/// @param _counterpart The address of `L2LidoGateway` contract in L2.
42+
/// @param _router The address of `L1GatewayRouter` contract.
43+
/// @param _messenger The address of `L1ScrollMessenger` contract.
44+
constructor(
45+
address _l1Token,
46+
address _l2Token,
47+
address _counterpart,
48+
address _router,
49+
address _messenger
50+
) LidoBridgeableTokens(_l1Token, _l2Token) ScrollGatewayBase(_counterpart, _router, _messenger) {
51+
if (_l1Token == address(0) || _l2Token == address(0) || _router == address(0)) {
52+
revert ErrorZeroAddress();
53+
}
54+
55+
_disableInitializers();
56+
}
57+
58+
/// @notice Initialize the storage of L1LidoGateway v1.
59+
///
60+
/// @dev The parameters `_counterpart`, `_router` and `_messenger` are no longer used.
61+
///
62+
/// @param _counterpart The address of `L2LidoGateway` contract in L2.
63+
/// @param _router The address of `L1GatewayRouter` contract.
64+
/// @param _messenger The address of `L1ScrollMessenger` contract.
65+
function initialize(
66+
address _counterpart,
67+
address _router,
68+
address _messenger
69+
) external initializer {
70+
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
71+
}
72+
73+
/// @notice Initialize the storage of L1LidoGateway v2.
74+
/// @param _depositsEnabler The address of user who can enable deposits
75+
/// @param _depositsEnabler The address of user who can disable deposits
76+
/// @param _withdrawalsEnabler The address of user who can enable withdrawals
77+
/// @param _withdrawalsDisabler The address of user who can disable withdrawals
78+
function initializeV2(
79+
address _depositsEnabler,
80+
address _depositsDisabler,
81+
address _withdrawalsEnabler,
82+
address _withdrawalsDisabler
83+
) external reinitializer(2) {
84+
__LidoGatewayManager_init(_depositsEnabler, _depositsDisabler, _withdrawalsEnabler, _withdrawalsDisabler);
85+
}
86+
87+
/*************************
88+
* Public View Functions *
89+
*************************/
90+
91+
/// @inheritdoc IL1ERC20Gateway
92+
function getL2ERC20Address(address _l1Token)
93+
external
94+
view
95+
override
96+
onlySupportedL1Token(_l1Token)
97+
returns (address)
98+
{
99+
return l2Token;
100+
}
101+
102+
/**********************
103+
* Internal Functions *
104+
**********************/
105+
106+
/// @inheritdoc L1ERC20Gateway
107+
/// @dev The length of `_data` always be zero, which guarantee by `L2LidoGateway`.
108+
function _beforeFinalizeWithdrawERC20(
109+
address _l1Token,
110+
address _l2Token,
111+
address,
112+
address,
113+
uint256,
114+
bytes calldata
115+
) internal virtual override onlySupportedL1Token(_l1Token) onlySupportedL2Token(_l2Token) whenWithdrawalsEnabled {
116+
if (msg.value != 0) revert ErrorNonZeroMsgValue();
117+
}
118+
119+
/// @inheritdoc L1ERC20Gateway
120+
function _beforeDropMessage(
121+
address _token,
122+
address,
123+
uint256
124+
) internal virtual override onlySupportedL1Token(_token) {
125+
if (msg.value != 0) revert ErrorNonZeroMsgValue();
126+
}
127+
128+
/// @inheritdoc L1ERC20Gateway
129+
function _deposit(
130+
address _token,
131+
address _to,
132+
uint256 _amount,
133+
bytes memory _data,
134+
uint256 _gasLimit
135+
) internal virtual override nonReentrant onlySupportedL1Token(_token) onlyNonZeroAccount(_to) whenDepositsEnabled {
136+
if (_amount == 0) revert ErrorDepositZeroAmount();
137+
138+
// 1. Transfer token into this contract.
139+
address _from;
140+
(_from, _amount, _data) = _transferERC20In(_token, _amount, _data);
141+
if (_data.length != 0) revert DepositAndCallIsNotAllowed();
142+
143+
// 2. Generate message passed to L2LidoGateway.
144+
bytes memory _message = abi.encodeCall(
145+
IL2ERC20Gateway.finalizeDepositERC20,
146+
(_token, l2Token, _from, _to, _amount, _data)
147+
);
148+
149+
// 3. Send message to L1ScrollMessenger.
150+
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit, _from);
151+
152+
emit DepositERC20(_token, l2Token, _from, _to, _amount, _data);
153+
}
154+
}

0 commit comments

Comments
 (0)