diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 643c6a7..1f5b358 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,5 +21,8 @@ jobs: with: version: nightly - - name: Run tests - run: forge test -vvv --fork-url https://rpc.lyra.finance \ No newline at end of file + - name: Run lyra fork tests + run: forge test --match-contract FORK_LYRA_ --fork-url https://rpc.lyra.finance + + - name: Run Mainnet fork tests + run: forge test --match-contract FORK_MAINNET_ --fork-url https://mainnet.infura.io/v3/743507feddbd4a8088614092511076bc -vvv \ No newline at end of file diff --git a/README.md b/README.md index 9ce7c19..f115d5b 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,83 @@ $ forge build ### Test +We have a few fork tests for deposit and withdraw helpers. You can run them on Mainnet / Lyra chain with commands below: + ```shell # tests on Lyra chain -$ forge test --fork-url +$ forge test --match-contract FORK_LYRA_ --fork-url https://rpc.lyra.finance + +# tests on Mainnet +$ forge test --match-contract FORK_MAINNET_ --fork-url ``` ### Using the contracts -Go see [this repo](https://github.com/antoncoding/lyra-aa-example) for examples \ No newline at end of file +Go see [this repo](https://github.com/antoncoding/lyra-aa-example) for examples + + +# Deployments + +## Gasless Deposit Forwarders +Gasless forwarders are used to make sure users only with ERC20 can deposit to Lyra + +### ETH +| Network | USDC Selfpaying Forwarder | USDC Sponsored Forwarder | +| -------- | -------- | --- | +| Ethereum Mainnet | [0x00efac83a3168568e258ab1ec85e85c10cbaf74e](https://etherscan.io/address/0x00efac83a3168568e258ab1ec85e85c10cbaf74e#code) | [0xf0372da389db728a3173a7b91c5cb4437a6319ea](https://etherscan.io/address/0xf0372da389db728a3173a7b91c5cb4437a6319ea)| + +* On Ethereum Mainnet, only USDC supports gasless deposit. +* We use a differnet forwarder here that only work with USDC (`receiveWithAuth`) to minimize gas cost. + +### Arbitrum + +| Network | SelfPaying Permit Forwarder | Sponsored Permit Forwarder | +| -------- | -------- | --- | +| Arbitrum | [0x00eFAc83a3168568e258ab1Ec85E85C10cBAf74E](https://arbiscan.io/address/0x00eFAc83a3168568e258ab1Ec85E85C10cBAf74E) | [0xC3621651c550F3c1BC146ffAe0975a566423Da17](https://arbiscan.io/address/0xC3621651c550F3c1BC146ffAe0975a566423Da17) | +| Arbitrum Sepolia | - | [0xE3436F0F982fbbAf88f28DACE9b36a85c97aECdE](https://sepolia.arbiscan.io/address/0xE3436F0F982fbbAf88f28DACE9b36a85c97aECdE) | + +* On Arbitrum, all ERC20s can be gasless (with `permit`) + + ### Optimism +| Network | SelfPaying Permit Forwarder | Sponsored Permit Forwarder | +| -------- | -------- | --- | +| Optimism | [0xAa7Dd6fa6B604b776BCE03Af6ED717c00E66538E](https://optimistic.etherscan.io/address/0xAa7Dd6fa6B604b776BCE03Af6ED717c00E66538E#code) | [0x062B67001A6dd9FC6Aa1CFB9c246AcfFC4BfAdC5](https://optimistic.etherscan.io/address/0x062B67001A6dd9FC6Aa1CFB9c246AcfFC4BfAdC5#code) | +| Optimism Sepolia | - | [0x1480Cfe30213b134f757757d328949AAe406eA33](https://sepolia-optimistic.etherscan.io/address/0x1480Cfe30213b134f757757d328949AAe406eA33#code) | + + +* On Optimism, only USDC has `permit` now, but other ERC20s can potentially use these contracts to achieve gasless deposit if they have permit. + + +## Deposit Helper + +### `LyraDepositWrapper` + +Help wrapping ETH and deposit with socket vault in one go. Can also be used with ERC20 deposits to calculate L2 address + + +### Mainnet +| Network | Address | +| -------- | -------- | +| Ethereum | [0x18a0f3F937DD0FA150d152375aE5A4E941d1527b](https://etherscan.io/address/0x18a0f3f937dd0fa150d152375ae5a4e941d1527b#code) | +| Optimism | [0xC65005131Cfdf06622b99E8E17f72Cf694b586cC](https://optimistic.etherscan.io/address/0xC65005131Cfdf06622b99E8E17f72Cf694b586cC#code) | +| Arbitrum | [0x076BB6117750e80AD570D98891B68da86C203A88](https://arbiscan.io/address/0x076BB6117750e80AD570D98891B68da86C203A88#readContract) | + +### Testnet +| Network | Address | +| -------- | -------- | +| Sepolia | [0x46e75b6983126896227a5717f2484efb04a0c151](https://sepolia.etherscan.io/address/0x46e75b6983126896227a5717f2484efb04a0c151#readContract) | +| Op-Sepolia | [0x3E7DEc059a3692c184BF0D0AC3d9Af7570DF6A3c](https://sepolia-optimistic.etherscan.io/address/0x3E7DEc059a3692c184BF0D0AC3d9Af7570DF6A3c#code) | +| Arbi-Sepolia | [0x5708bDE1c5e49b62cfd46D07b5cd3c898930Ef23](https://sepolia.arbiscan.io/address/0x5708bDE1c5e49b62cfd46D07b5cd3c898930Ef23#readContract) | + + + +## Withdraw Helper + +### `WithdrawHelperV2` + +withdraw ERC20s from Lyra Chain back to Mainnet / L2s, paid socket fee in token. + +| Network | Address | +| -------- | -------- | +| Lyra | [0x0E4e5779F633F823d007f3C27fa6feFb22B45316](https://explorer.lyra.finance/address/0x0E4e5779F633F823d007f3C27fa6feFb22B45316) | +| Lyra Testnet | [0xD7080B2399B88c3520F8F793f4758D0C6ccDf48a](https://explorerl2new-prod-testnet-0eakp60405.t.conduit.xyz/address/0xD7080B2399B88c3520F8F793f4758D0C6ccDf48a) | diff --git a/foundry.toml b/foundry.toml index 16eb0b7..8b206ce 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,6 +9,5 @@ optimizer_runs = 2000 [rpc_endpoints] mainnet = "https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}" -op_goerli = "https://optimism-goerli.infura.io/v3/ea21b2313cdf43c28f25b849d7a75274" lyra = "https://rpc.lyra.finance" lyra_testnet = "https://l2-prod-testnet-0eakp60405.t.conduit.xyz" \ No newline at end of file diff --git a/src/helpers/LyraDepositWrapper.sol b/src/helpers/LyraDepositWrapper.sol new file mode 100644 index 0000000..9d50abc --- /dev/null +++ b/src/helpers/LyraDepositWrapper.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {IERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {IWETH} from "../interfaces/IWETH.sol"; +import {ISocketVault} from "../interfaces/ISocketVault.sol"; +import {ILightAccountFactory} from "../interfaces/ILightAccountFactory.sol"; + +/** + * @title LyraDepositWrapper + * @dev Helper contract to wrap ETH into L2 WETH, or deposit any token to L2 smart contract wallet address + */ +contract LyraDepositWrapper { + ///@dev L2 USDC address. + address public immutable weth; + + ///@dev Light Account factory address. + address public constant lightAccountFactory = 0x000000893A26168158fbeaDD9335Be5bC96592E2; + + constructor(address _weth) { + weth = _weth; + } + + /** + * @notice Wrap ETH into WETH and deposit to Lyra Chain via socket vault + */ + function depositETHToLyra(address socketVault, bool isSCW, uint256 gasLimit, address connector) external payable { + uint256 socketFee = ISocketVault(socketVault).getMinFees(connector, gasLimit); + + uint256 depositAmount = msg.value - socketFee; + + IWETH(weth).deposit{value: depositAmount}(); + IERC20(weth).approve(socketVault, type(uint256).max); + + address recipient = _getL2Receiver(isSCW); + + ISocketVault(socketVault).depositToAppChain{value: socketFee}(recipient, depositAmount, gasLimit, connector); + } + + /** + * @notice Deposit any token to Lyra Chain via socket vault. + * @dev This function help calculate L2 smart wallet addresses for users + */ + function depositToLyra( + address token, + address socketVault, + bool isSCW, + uint256 amount, + uint256 gasLimit, + address connector + ) external payable { + IERC20(token).transferFrom(msg.sender, address(this), amount); + IERC20(token).approve(socketVault, type(uint256).max); + + address recipient = _getL2Receiver(isSCW); + + ISocketVault(socketVault).depositToAppChain{value: msg.value}(recipient, amount, gasLimit, connector); + } + + /** + * @notice Return the receiver address on L2 + */ + function _getL2Receiver(bool isScwWallet) internal view returns (address) { + if (isScwWallet) { + return ILightAccountFactory(lightAccountFactory).getAddress(msg.sender, 0); + } else { + return msg.sender; + } + } + + receive() external payable {} +} diff --git a/src/interfaces/IWETH.sol b/src/interfaces/IWETH.sol new file mode 100644 index 0000000..0a8da8e --- /dev/null +++ b/src/interfaces/IWETH.sol @@ -0,0 +1,7 @@ +pragma solidity >=0.5.0; + +interface IWETH { + function deposit() external payable; + function transfer(address to, uint256 value) external returns (bool); + function withdraw(uint256) external; +} diff --git a/test/fork-tests/deposit/DepositHelper.t.sol b/test/fork-tests/deposit/DepositHelper.t.sol new file mode 100644 index 0000000..1677676 --- /dev/null +++ b/test/fork-tests/deposit/DepositHelper.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.18; + +import "lib/forge-std/src/Test.sol"; + +import {LyraDepositWrapper} from "src/helpers/LyraDepositWrapper.sol"; +import {IERC20} from "../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {IWETH} from "src/interfaces/IWETH.sol"; + +/** + * forge test --fork-url https://mainnet.infura.io/v3/${INFURA_PROJECT_ID} -vvv + */ +contract FORK_MAINNET_LyraDepositWrapper is Test { + address public weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + + address public wethVault = address(0xD4efe33C66B8CdE33B8896a2126E41e5dB571b7e); + address public wethConnector = address(0xCf814e58f1649F94d37E51f730D6bF72409fA09c); + + LyraDepositWrapper public wrapper; + + uint256 public alicePk = 0xbabebabe; + address public alice = vm.addr(alicePk); + + /** + * Only run the test when running with --fork flag, and connected to Lyra mainnet + */ + modifier onlyMainnet() { + if (block.chainid != 1) return; + _; + } + + function setUp() public onlyMainnet { + wrapper = new LyraDepositWrapper(weth); + + vm.deal(alice, 100 ether); + } + + function test_deposit_ETH() public onlyMainnet { + vm.prank(alice); + + wrapper.depositETHToLyra{value: 20 ether}(wethVault, true, 200_000, wethConnector); + } + + function test_deposit_WETH() public onlyMainnet { + vm.startPrank(alice); + IWETH(weth).deposit{value: 20 ether}(); + IERC20(weth).approve(address(wrapper), type(uint256).max); + + uint256 socketFee = 0.03 ether; + + wrapper.depositToLyra{value: socketFee}(weth, wethVault, true, 20 ether, 200_000, wethConnector); + vm.stopPrank(); + } + + receive() external payable {} +} diff --git a/test/fork-tests/gelato/.gitkeep b/test/fork-tests/gelato/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fork-tests/gelato/SelfPayingForwarder.t.sol b/test/fork-tests/gelato/SelfPayingForwarder.t.sol deleted file mode 100644 index d9f7159..0000000 --- a/test/fork-tests/gelato/SelfPayingForwarder.t.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -import "lib/forge-std/src/Test.sol"; -import "lib/forge-std/src/console2.sol"; - -import "src/gelato/LyraSelfPayingForwarder.sol"; -import {USDC} from "src/mocks/USDC.sol"; - -contract FORK_SelfPayingForwarderTest is Test { - address public immutable gelato = address(0x3CACa7b48D0573D793d3b0279b5F0029180E83b6); - - address public immutable gelatoRelayer = address(0xb539068872230f20456CF38EC52EF2f91AF4AE49); - - address public immutable usdcMainnet = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); - - // all transactions go from gelato => gelatoRelayer -> forwarder - - LyraSelfPayingForwarder public forwarder; - - uint256 public alicePk = 0xbabebabe; - address public alice = vm.addr(alicePk); - - modifier onlyMainnet() { - _; - if (block.chainid != 1) return; - } - - // function setUp() public onlyMainnet { - // // deploy test contract - // forwarder = new LyraSelfPayingForwarder( - // 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // mainnet USDC - // 0x7F5c764cBc14f9669B88837ca1490cCa17c31607, // OP USDC (Bridged) - // 0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1, // OP bridge on mainnet - // 0x0000000000000000000000000000000000000000 - // ); - - // _mintMainnetUSDC(alice, 1e6 * 1e6); - // } - - // function test_fork_SelfPayingForwarder() public onlyMainnet { - // assertFalse(address(forwarder) == address(0)); - // } - - // function test_fork_depositFromEOA() public onlyMainnet { - // // alice sign transfer with auth - - // // call forwarder - // } - - function _mintMainnetUSDC(address account, uint256 amount) public { - vm.prank(0xE982615d461DD5cD06575BbeA87624fda4e3de17); // masterMinter for USDC - USDC(usdcMainnet).configureMinter(address(this), 5000e18); - - // mint from address(this) - USDC(usdcMainnet).mint(account, amount); - } - - function _sendTxAsGelatoRelayer() public { - vm.startPrank(gelatoRelayer); - // attach sender info at end of tx - vm.stopPrank(); - } -} diff --git a/test/fork-tests/withdraw/LyraWithdrawWrapper.t.sol b/test/fork-tests/withdraw/LyraWithdrawWrapper.t.sol index 69a9fc2..65188cb 100644 --- a/test/fork-tests/withdraw/LyraWithdrawWrapper.t.sol +++ b/test/fork-tests/withdraw/LyraWithdrawWrapper.t.sol @@ -9,7 +9,7 @@ import {USDC} from "src/mocks/USDC.sol"; /** * forge test --fork-url https://rpc.lyra.finance -vvv */ -contract FORK_LyraWithdrawalTest is Test { +contract FORK_LYRA_LyraWithdrawalTest is Test { address public immutable usdc = address(0x6879287835A86F50f784313dBEd5E5cCC5bb8481); address public immutable controller = address(0x4C9faD010D8be90Aba505c85eacc483dFf9b8Fa9); diff --git a/test/fork-tests/withdraw/LyraWithdrawWrapperV2.t.sol b/test/fork-tests/withdraw/LyraWithdrawWrapperV2.t.sol index 6dc42e9..7ca92d9 100644 --- a/test/fork-tests/withdraw/LyraWithdrawWrapperV2.t.sol +++ b/test/fork-tests/withdraw/LyraWithdrawWrapperV2.t.sol @@ -9,13 +9,15 @@ import {USDC} from "src/mocks/USDC.sol"; /** * forge test --fork-url https://rpc.lyra.finance -vvv */ -contract FORK_LyraWithdrawalV2Test is Test { +contract FORK_LYRA_LyraWithdrawalV2Test is Test { address public usdc = address(0x6879287835A86F50f784313dBEd5E5cCC5bb8481); // withdraw as official USDC address public usdcController = address(0x4C9faD010D8be90Aba505c85eacc483dFf9b8Fa9); address public usdc_Mainnet_Connector = address(0x1281C1464449DB73bdAa30928BCC63Dc25D8D187); - address public usdc_Arbi_Connector = address(0xBdE9e687F3A23Ebbc972c58D510dfc1f58Fb35EF); // as native USDC + + // withdraw as USDC.e + address public usdc_Arbi_Connector = address(0xFc1e42b0C3Ff8d69FBe639a6a674fF5f0FcE778D); // wbtc asset address public wBTC = address(0x9b80ab732a6F1030326Af0014f106E12C4Db18EC); @@ -74,7 +76,6 @@ contract FORK_LyraWithdrawalV2Test is Test { } function test_fork_RevertIf_tokenMismatch() public onlyLyra { - // _mintLyraUSDC(address(wrapper), 1000e6); uint256 amount = 100e6; vm.startPrank(alice);