Skip to content

Commit

Permalink
add fork integration for lido gateways
Browse files Browse the repository at this point in the history
  • Loading branch information
zimpha committed Nov 14, 2023
1 parent 581f2e4 commit f08e0d8
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 1 deletion.
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
40 changes: 40 additions & 0 deletions contracts/src/test/integration/Domain.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.0;

import {console2} from "forge-std/console2.sol";
import {StdChains} from "forge-std/StdChains.sol";
import {Vm} from "forge-std/Vm.sol";

// code from: https://github.com/marsfoundation/xchain-helpers/blob/master/src/testing/Domain.sol

contract Domain {
// solhint-disable-next-line const-name-snakecase
Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

StdChains.Chain private _details;
uint256 public forkId;

constructor(StdChains.Chain memory _chain) {
_details = _chain;
forkId = vm.createFork(_chain.rpcUrl);
vm.makePersistent(address(this));
}

function details() public view returns (StdChains.Chain memory) {
return _details;
}

function selectFork() public {
vm.selectFork(forkId);
require(
block.chainid == _details.chainId,
string(
abi.encodePacked(_details.chainAlias, " is pointing to the wrong RPC endpoint '", _details.rpcUrl, "'")
)
);
}

function rollFork(uint256 blocknum) public {
vm.rollFork(forkId, blocknum);
}
}
129 changes: 129 additions & 0 deletions contracts/src/test/integration/GatewayIntegrationBase.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: MIT

pragma solidity =0.8.16;

import {Test} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";

import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

import {Domain} from "./Domain.t.sol";

import {IL2ScrollMessenger} from "../../L2/IL2ScrollMessenger.sol";
import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol";

abstract contract GatewayIntegrationBase is Test {
bytes32 private constant SENT_MESSAGE_TOPIC =
keccak256("SentMessage(address,address,uint256,uint256,uint256,bytes)");

address internal constant L1_SCROLL_MESSENGER = 0x6774Bcbd5ceCeF1336b5300fb5186a12DDD8b367;

address internal constant L1_SCROLL_CHAIN = 0xa13BAF47339d63B743e7Da8741db5456DAc1E556;

address internal constant L1_MESSAGE_QUEUE = 0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B;

address internal constant L1_GATEWAY_ROUTER = 0xF8B1378579659D8F7EE5f3C929c2f3E332E41Fd6;

address internal constant L2_SCROLL_MESSENGER = 0x781e90f1c8Fc4611c9b7497C3B47F99Ef6969CbC;

address internal constant L2_MESSAGE_QUEUE = 0x5300000000000000000000000000000000000000;

address internal constant L2_GATEWAY_ROUTER = 0x4C0926FF5252A435FD19e10ED15e5a249Ba19d79;

Domain internal mainnet;

Domain internal scroll;

uint256 internal lastFromMainnetLogIndex;

uint256 internal lastFromScrollLogIndex;

receive() external payable {}

// solhint-disable-next-line func-name-mixedcase
function __GatewayIntegrationBase_setUp() internal {
setChain("scroll", ChainData("Scroll Chain", 534352, "https://rpc.scroll.io"));
setChain("mainnet", ChainData("Mainnet", 1, "https://rpc.ankr.com/eth"));

mainnet = new Domain(getChain("mainnet"));
scroll = new Domain(getChain("scroll"));
}

function relayFromMainnet() internal {
scroll.selectFork();

address malias = AddressAliasHelper.applyL1ToL2Alias(L1_SCROLL_MESSENGER);

// Read all L1 -> L2 messages and relay them under Optimism fork
Vm.Log[] memory allLogs = vm.getRecordedLogs();
for (; lastFromMainnetLogIndex < allLogs.length; lastFromMainnetLogIndex++) {
Vm.Log memory _log = allLogs[lastFromMainnetLogIndex];
if (_log.topics[0] == SENT_MESSAGE_TOPIC && _log.emitter == address(L1_SCROLL_MESSENGER)) {
address sender = address(uint160(uint256(_log.topics[1])));
address target = address(uint160(uint256(_log.topics[2])));
(uint256 value, uint256 nonce, uint256 gasLimit, bytes memory message) = abi.decode(
_log.data,
(uint256, uint256, uint256, bytes)
);
vm.prank(malias);
IL2ScrollMessenger(L2_SCROLL_MESSENGER).relayMessage{gas: gasLimit}(
sender,
target,
value,
nonce,
message
);
}
}
}

function relayFromScroll() internal {
mainnet.selectFork();

// Read all L2 -> L1 messages and relay them under Primary fork
// Note: We bypass the L1 messenger relay here because it's easier to not have to generate valid state roots / merkle proofs
Vm.Log[] memory allLogs = vm.getRecordedLogs();
for (; lastFromScrollLogIndex < allLogs.length; lastFromScrollLogIndex++) {
Vm.Log memory _log = allLogs[lastFromScrollLogIndex];
if (_log.topics[0] == SENT_MESSAGE_TOPIC && _log.emitter == address(L2_SCROLL_MESSENGER)) {
address sender = address(uint160(uint256(_log.topics[1])));
address target = address(uint160(uint256(_log.topics[2])));
(uint256 value, , , bytes memory message) = abi.decode(_log.data, (uint256, uint256, uint256, bytes));
// Set xDomainMessageSender
vm.store(address(L1_SCROLL_MESSENGER), bytes32(uint256(201)), bytes32(uint256(uint160(sender))));
vm.startPrank(address(L1_SCROLL_MESSENGER));
(bool success, bytes memory response) = target.call{value: value}(message);
vm.stopPrank();
vm.store(address(L1_SCROLL_MESSENGER), bytes32(uint256(201)), bytes32(uint256(1)));
if (!success) {
assembly {
revert(add(response, 32), mload(response))
}
}
}
}
}

function upgrade(
bool isMainnet,
address proxy,
address implementation
) internal {
address admin;
address owner;
if (isMainnet) {
mainnet.selectFork();
admin = 0xEB803eb3F501998126bf37bB823646Ed3D59d072;
owner = 0x798576400F7D662961BA15C6b3F3d813447a26a6;
} else {
scroll.selectFork();
admin = 0xA76acF000C890b0DD7AEEf57627d9899F955d026;
owner = 0x13D24a7Ff6F5ec5ff0e9C40Fc3B8C9c01c65437B;
}

vm.startPrank(owner);
ProxyAdmin(admin).upgrade(ITransparentUpgradeableProxy(proxy), implementation);
vm.stopPrank();
}
}
114 changes: 114 additions & 0 deletions contracts/src/test/integration/LidoGatewayIntegration.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT

pragma solidity =0.8.16;

import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol";

import {GatewayIntegrationBase} from "./GatewayIntegrationBase.t.sol";

import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
import {L1LidoGateway} from "../../lido/L1LidoGateway.sol";
import {L2LidoGateway} from "../../lido/L2LidoGateway.sol";

interface IWstETH {
function wrap(uint256 _stETHAmount) external returns (uint256);

function unwrap(uint256 _wstETHAmount) external returns (uint256);

function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256);

function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);

function stEthPerToken() external view returns (uint256);

function tokensPerStEth() external view returns (uint256);
}

contract LidoGatewayIntegrationTest is GatewayIntegrationBase {
address private constant L1_LIDO_GATEWAY = 0x6625C6332c9F91F2D27c304E729B86db87A3f504;

address private constant L1_STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;

address private constant L1_WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;

address private constant L2_LIDO_GATEWAY = 0x8aE8f22226B9d789A36AC81474e633f8bE2856c9;

address private constant L2_WSTETH = 0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32;

function setUp() public {
__GatewayIntegrationBase_setUp();

mainnet.selectFork();
upgrade(true, L1_LIDO_GATEWAY, address(new L1LidoGateway(L1_WSTETH, L2_WSTETH)));
L1LidoGateway(L1_LIDO_GATEWAY).initializeV2();

scroll.selectFork();
upgrade(false, L2_LIDO_GATEWAY, address(new L2LidoGateway(L1_WSTETH, L2_WSTETH)));
L2LidoGateway(L2_LIDO_GATEWAY).initializeV2();
}

function testWithoutRouter() public {
depositAndWithdraw(false);
}

function testWithRouter() public {
depositAndWithdraw(true);
}

function depositAndWithdraw(bool useRouter) private {
vm.recordLogs();

mainnet.selectFork();
uint256 rate = IWstETH(L1_WSTETH).stEthPerToken();

// deposit to get some stETH
(bool succeed, ) = L1_STETH.call{value: 11 * rate}("");
assertEq(true, succeed);
assertApproxEqAbs(MockERC20(L1_STETH).balanceOf(address(this)), 11 * rate, 10);

// wrap stETH to wstETH
MockERC20(L1_STETH).approve(L1_WSTETH, 10 * rate);
IWstETH(L1_WSTETH).wrap(10 * rate);
assertApproxEqAbs(MockERC20(L1_WSTETH).balanceOf(address(this)), 10 ether, 10);

// deposit 1 wstETH
uint256 l1GatewayBalance = MockERC20(L1_WSTETH).balanceOf(L1_LIDO_GATEWAY);
uint256 l1Balance = MockERC20(L1_WSTETH).balanceOf(address(this));
if (useRouter) {
MockERC20(L1_WSTETH).approve(L1_GATEWAY_ROUTER, 1 ether);
IL1ERC20Gateway(L1_GATEWAY_ROUTER).depositERC20{value: 1 ether}(L1_WSTETH, 1 ether, 400000);
} else {
MockERC20(L1_WSTETH).approve(L1_LIDO_GATEWAY, 1 ether);
IL1ERC20Gateway(L1_LIDO_GATEWAY).depositERC20{value: 1 ether}(L1_WSTETH, 1 ether, 400000);
}
assertEq(l1Balance - 1 ether, MockERC20(L1_WSTETH).balanceOf(address(this)));
assertEq(l1GatewayBalance + 1 ether, MockERC20(L1_WSTETH).balanceOf(L1_LIDO_GATEWAY));

// relay message to Scroll and check balance
scroll.selectFork();
uint256 l2Balance = MockERC20(L2_WSTETH).balanceOf(address(this));
relayFromMainnet();

// withdraw wstETH
scroll.selectFork();
assertEq(l2Balance + 1 ether, MockERC20(L2_WSTETH).balanceOf(address(this)));
assertEq(0, MockERC20(L2_WSTETH).balanceOf(L2_LIDO_GATEWAY));
if (useRouter) {
IL2ERC20Gateway(L2_GATEWAY_ROUTER).withdrawERC20(L2_WSTETH, 1 ether, 0);
} else {
IL2ERC20Gateway(L2_LIDO_GATEWAY).withdrawERC20(L2_WSTETH, 1 ether, 0);
}
assertEq(l2Balance, MockERC20(L2_WSTETH).balanceOf(address(this)));
assertEq(0, MockERC20(L2_WSTETH).balanceOf(L2_LIDO_GATEWAY));

// relay message to Mainnet and check balance
mainnet.selectFork();
l1GatewayBalance = MockERC20(L1_WSTETH).balanceOf(L1_LIDO_GATEWAY);
l1Balance = MockERC20(L1_WSTETH).balanceOf(address(this));
relayFromScroll();
mainnet.selectFork();
assertEq(l1Balance + 1 ether, MockERC20(L1_WSTETH).balanceOf(address(this)));
assertEq(l1GatewayBalance - 1 ether, MockERC20(L1_WSTETH).balanceOf(L1_LIDO_GATEWAY));
}
}
4 changes: 4 additions & 0 deletions contracts/src/test/mocks/MockL1LidoGateway.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// SPDX-License-Identifier: MIT

pragma solidity =0.8.16;

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

contract MockL1LidoGateway is L1LidoGateway {
Expand Down
4 changes: 4 additions & 0 deletions contracts/src/test/mocks/MockL2LidoGateway.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// SPDX-License-Identifier: MIT

pragma solidity =0.8.16;

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

contract MockL2LidoGateway is L2LidoGateway {
Expand Down

0 comments on commit f08e0d8

Please sign in to comment.