-
Notifications
You must be signed in to change notification settings - Fork 616
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add fork integration for lido gateways
- Loading branch information
Showing
6 changed files
with
291 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
129
contracts/src/test/integration/GatewayIntegrationBase.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
114
contracts/src/test/integration/LidoGatewayIntegration.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters