Skip to content

Commit

Permalink
feat(contracts): lido gateway (#988)
Browse files Browse the repository at this point in the history
  • Loading branch information
zimpha authored Jan 16, 2024
1 parent 317ba26 commit 69224eb
Show file tree
Hide file tree
Showing 18 changed files with 2,691 additions and 1 deletion.
10 changes: 10 additions & 0 deletions contracts/.solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
skipFiles: [
'mocks',
'test',
'L2/predeploys/L1BlockContainer.sol',
'libraries/verifier/ZkTrieVerifier.sol',
'libraries/verifier/PatriciaMerkleTrieVerifier.sol'
],
istanbulReporter: ["lcov", "json"]
};
1 change: 0 additions & 1 deletion contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ sender = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # the address of `
tx_origin = '0x00a329c0648769a73afac7f9381e08fb43dbea72' # the address of `tx.origin` in tests
initial_balance = '0xffffffffffffffffffffffff' # the initial balance of the test contract
block_number = 0 # the block number we are at in tests
chain_id = 99 # the chain id we are on in tests
gas_limit = 9223372036854775807 # the gas limit in tests
gas_price = 0 # the gas price (in wei) in tests
block_base_fee_per_gas = 0 # the base fee (in wei) in tests
Expand Down
79 changes: 79 additions & 0 deletions contracts/integration-test/GasOptimizationUpgrade.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,46 @@ describe("GasOptimizationUpgrade.spec", async () => {
"L1GatewayRouter.depositERC20 USDC after upgrade"
);
});

it.skip("should succeed on L1LidoGateway", async () => {
const L1_WSTETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0";
const L2_WSTETH = "0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32";
const L1_GATEWAY = "0x6625C6332c9F91F2D27c304E729B86db87A3f504";
const L2_GATEWAY = "0x8aE8f22226B9d789A36AC81474e633f8bE2856c9";
const L1LidoGateway = await ethers.getContractFactory("L1LidoGateway", deployer);
const impl = await L1LidoGateway.deploy(L1_WSTETH, L2_WSTETH, L2_GATEWAY, L1_ROUTER, L1_MESSENGER);
const gateway = await ethers.getContractAt("L1LidoGateway", L1_GATEWAY, deployer);
const amountIn = ethers.utils.parseUnits("1", 6);
const fee = await queue.estimateCrossDomainMessageFee(1e6);
const token = await ethers.getContractAt("MockERC20", L1_WSTETH, deployer);
await mockERC20Balance(token.address, amountIn.mul(10), 0);
await token.approve(L1_GATEWAY, constants.MaxUint256);
await token.approve(L1_ROUTER, constants.MaxUint256);

// before upgrade
await showGasUsage(
await gateway["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
"L1LidoGateway.depositERC20 wstETH before upgrade"
);
await showGasUsage(
await router["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
"L1GatewayRouter.depositERC20 wstETH before upgrade"
);

// do upgrade
await upgradeL1(L1_GATEWAY, impl.address);
await gateway.initializeV2(deployer.address, deployer.address, deployer.address, deployer.address);

// after upgrade
await showGasUsage(
await gateway["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
"L1LidoGateway.depositERC20 wstETH after upgrade"
);
await showGasUsage(
await router["depositERC20(address,uint256,uint256)"](L1_WSTETH, amountIn, 1e6, { value: fee }),
"L1GatewayRouter.depositERC20 wstETH after upgrade"
);
});
});

context("L2 upgrade", async () => {
Expand Down Expand Up @@ -584,5 +624,44 @@ describe("GasOptimizationUpgrade.spec", async () => {
"L2GatewayRouter.withdrawERC20 USDC after upgrade"
);
});

it.skip("should succeed on L2LidoGateway", async () => {
const L1_WSTETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0";
const L2_WSTETH = "0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32";
const L1_GATEWAY = "0x6625C6332c9F91F2D27c304E729B86db87A3f504";
const L2_GATEWAY = "0x8aE8f22226B9d789A36AC81474e633f8bE2856c9";
const L2LidoGateway = await ethers.getContractFactory("L2LidoGateway", deployer);
const impl = await L2LidoGateway.deploy(L1_WSTETH, L2_WSTETH, L1_GATEWAY, L2_ROUTER, L2_MESSENGER);
const gateway = await ethers.getContractAt("L2LidoGateway", L2_GATEWAY, deployer);
const amountIn = ethers.utils.parseUnits("1", 6);
const token = await ethers.getContractAt("MockERC20", L2_WSTETH, deployer);
await mockERC20Balance(token.address, amountIn.mul(10), 51);
await token.approve(L2_GATEWAY, constants.MaxUint256);
await token.approve(L2_ROUTER, constants.MaxUint256);

// before upgrade
await showGasUsage(
await gateway["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
"L2LidoGateway.withdrawERC20 wstETH before upgrade"
);
await showGasUsage(
await router["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
"L2GatewayRouter.withdrawERC20 wstETH before upgrade"
);

// do upgrade
await upgradeL2(L2_GATEWAY, impl.address);
await gateway.initializeV2(deployer.address, deployer.address, deployer.address, deployer.address);

// after upgrade
await showGasUsage(
await gateway["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
"L2LidoGateway.withdrawERC20 wstETH after upgrade"
);
await showGasUsage(
await router["withdrawERC20(address,uint256,uint256)"](L2_WSTETH, amountIn, 1e6),
"L2GatewayRouter.withdrawERC20 wstETH after upgrade"
);
});
});
});
63 changes: 63 additions & 0 deletions contracts/scripts/foundry/DeployLidoGateway.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";

import {L1LidoGateway} from "../../src/lido/L1LidoGateway.sol";
import {L2LidoGateway} from "../../src/lido/L2LidoGateway.sol";

// solhint-disable state-visibility
// solhint-disable var-name-mixedcase

contract DeployLidoGateway is Script {
string NETWORK = vm.envString("NETWORK");

uint256 L1_DEPLOYER_PRIVATE_KEY = vm.envUint("L1_DEPLOYER_PRIVATE_KEY");

uint256 L2_DEPLOYER_PRIVATE_KEY = vm.envUint("L2_DEPLOYER_PRIVATE_KEY");

address L1_WSTETH_ADDR = vm.envAddress("L1_WSTETH_ADDR");

address L2_WSTETH_ADDR = vm.envAddress("L2_WSTETH_ADDR");

address L1_SCROLL_MESSENGER_PROXY_ADDR = vm.envAddress("L1_SCROLL_MESSENGER_PROXY_ADDR");
address L1_GATEWAY_ROUTER_PROXY_ADDR = vm.envAddress("L1_GATEWAY_ROUTER_PROXY_ADDR");
address L1_LIDO_GATEWAY_PROXY_ADDR = vm.envAddress("L1_LIDO_GATEWAY_PROXY_ADDR");

address L2_SCROLL_MESSENGER_PROXY_ADDR = vm.envAddress("L2_SCROLL_MESSENGER_PROXY_ADDR");
address L2_GATEWAY_ROUTER_PROXY_ADDR = vm.envAddress("L2_GATEWAY_ROUTER_PROXY_ADDR");
address L2_LIDO_GATEWAY_PROXY_ADDR = vm.envAddress("L2_LIDO_GATEWAY_PROXY_ADDR");

function run() external {
vm.startBroadcast(L2_DEPLOYER_PRIVATE_KEY);

if (keccak256(abi.encodePacked(NETWORK)) == keccak256(abi.encodePacked("L1"))) {
// deploy l1 lido gateway
L1LidoGateway gateway = new L1LidoGateway(
L1_WSTETH_ADDR,
L2_WSTETH_ADDR,
L2_LIDO_GATEWAY_PROXY_ADDR,
L1_GATEWAY_ROUTER_PROXY_ADDR,
L1_SCROLL_MESSENGER_PROXY_ADDR
);
logAddress("L1_LIDO_GATEWAY_IMPLEMENTATION_ADDR", address(gateway));
} else if (keccak256(abi.encodePacked(NETWORK)) == keccak256(abi.encodePacked("L2"))) {
// deploy l2 lido gateway
L2LidoGateway gateway = new L2LidoGateway(
L1_WSTETH_ADDR,
L2_WSTETH_ADDR,
L1_LIDO_GATEWAY_PROXY_ADDR,
L2_GATEWAY_ROUTER_PROXY_ADDR,
L2_SCROLL_MESSENGER_PROXY_ADDR
);
logAddress("L2_LIDO_GATEWAY_IMPLEMENTATION_ADDR", address(gateway));
}

vm.stopBroadcast();
}

function logAddress(string memory name, address addr) internal view {
console.log(string(abi.encodePacked(name, "=", vm.toString(address(addr)))));
}
}
154 changes: 154 additions & 0 deletions contracts/src/lido/L1LidoGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: MIT

pragma solidity =0.8.16;

import {IL1ERC20Gateway} from "../L1/gateways/IL1ERC20Gateway.sol";
import {L1ERC20Gateway} from "../L1/gateways/L1ERC20Gateway.sol";
import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol";
import {IL2ERC20Gateway} from "../L2/gateways/IL2ERC20Gateway.sol";
import {ScrollGatewayBase} from "../libraries/gateway/ScrollGatewayBase.sol";

import {LidoBridgeableTokens} from "./LidoBridgeableTokens.sol";
import {LidoGatewayManager} from "./LidoGatewayManager.sol";

contract L1LidoGateway is L1ERC20Gateway, LidoBridgeableTokens, LidoGatewayManager {
/**********
* Errors *
**********/

/// @dev Thrown when deposit zero amount token.
error ErrorDepositZeroAmount();

/// @dev Thrown when deposit erc20 with calldata.
error DepositAndCallIsNotAllowed();

/*************
* Variables *
*************/

/// @dev The initial version of `L1LidoGateway` use `L1CustomERC20Gateway`. We keep the storage
/// slot for `tokenMapping` for compatibility. It should no longer be used.
mapping(address => address) private __tokenMapping;

/***************
* Constructor *
***************/

/// @notice Constructor for `L1LidoGateway` implementation contract.
///
/// @param _l1Token The address of the bridged token in the L1 chain
/// @param _l2Token The address of the token minted on the L2 chain when token bridged
/// @param _counterpart The address of `L2LidoGateway` contract in L2.
/// @param _router The address of `L1GatewayRouter` contract.
/// @param _messenger The address of `L1ScrollMessenger` contract.
constructor(
address _l1Token,
address _l2Token,
address _counterpart,
address _router,
address _messenger
) LidoBridgeableTokens(_l1Token, _l2Token) ScrollGatewayBase(_counterpart, _router, _messenger) {
if (_l1Token == address(0) || _l2Token == address(0) || _router == address(0)) {
revert ErrorZeroAddress();
}

_disableInitializers();
}

/// @notice Initialize the storage of L1LidoGateway v1.
///
/// @dev The parameters `_counterpart`, `_router` and `_messenger` are no longer used.
///
/// @param _counterpart The address of `L2LidoGateway` contract in L2.
/// @param _router The address of `L1GatewayRouter` contract.
/// @param _messenger The address of `L1ScrollMessenger` contract.
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}

/// @notice Initialize the storage of L1LidoGateway v2.
/// @param _depositsEnabler The address of user who can enable deposits
/// @param _depositsEnabler The address of user who can disable deposits
/// @param _withdrawalsEnabler The address of user who can enable withdrawals
/// @param _withdrawalsDisabler The address of user who can disable withdrawals
function initializeV2(
address _depositsEnabler,
address _depositsDisabler,
address _withdrawalsEnabler,
address _withdrawalsDisabler
) external reinitializer(2) {
__LidoGatewayManager_init(_depositsEnabler, _depositsDisabler, _withdrawalsEnabler, _withdrawalsDisabler);
}

/*************************
* Public View Functions *
*************************/

/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Token)
external
view
override
onlySupportedL1Token(_l1Token)
returns (address)
{
return l2Token;
}

/**********************
* Internal Functions *
**********************/

/// @inheritdoc L1ERC20Gateway
/// @dev The length of `_data` always be zero, which guarantee by `L2LidoGateway`.
function _beforeFinalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address,
address,
uint256,
bytes calldata
) internal virtual override onlySupportedL1Token(_l1Token) onlySupportedL2Token(_l2Token) whenWithdrawalsEnabled {
if (msg.value != 0) revert ErrorNonZeroMsgValue();
}

/// @inheritdoc L1ERC20Gateway
function _beforeDropMessage(
address _token,
address,
uint256
) internal virtual override onlySupportedL1Token(_token) {
if (msg.value != 0) revert ErrorNonZeroMsgValue();
}

/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant onlySupportedL1Token(_token) onlyNonZeroAccount(_to) whenDepositsEnabled {
if (_amount == 0) revert ErrorDepositZeroAmount();

// 1. Transfer token into this contract.
address _from;
(_from, _amount, _data) = _transferERC20In(_token, _amount, _data);
if (_data.length != 0) revert DepositAndCallIsNotAllowed();

// 2. Generate message passed to L2LidoGateway.
bytes memory _message = abi.encodeCall(
IL2ERC20Gateway.finalizeDepositERC20,
(_token, l2Token, _from, _to, _amount, _data)
);

// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit, _from);

emit DepositERC20(_token, l2Token, _from, _to, _amount, _data);
}
}
Loading

0 comments on commit 69224eb

Please sign in to comment.