diff --git a/foundry.toml b/foundry.toml index 7d4a7dc..4707845 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,8 +5,6 @@ libs = ["lib"] via-ir = true -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options - - [lint] +ignore = ["src/vendor/*"] exclude_lints = ["unwrapped-modifier-logic"] \ No newline at end of file diff --git a/src/apps/Morpho.sol b/src/apps/Morpho.sol index fb1e653..e557475 100644 --- a/src/apps/Morpho.sol +++ b/src/apps/Morpho.sol @@ -249,9 +249,11 @@ contract HosyMorphoBorrow is HostMorphoUser { // borrow some amount of loanToken MORPHO.borrow(loadParams(), amount, 0, onBehalf, address(this)); + // TODO: complete implementation // User logic to use the tokens goes here. // Could send the tokens to the rollup via Passage, or do something // else :) + filler; return true; } diff --git a/src/chains/Pecorino.sol b/src/chains/Pecorino.sol index 8658524..a760618 100644 --- a/src/chains/Pecorino.sol +++ b/src/chains/Pecorino.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.13; import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; import {RollupPassage} from "zenith/src/passage/RollupPassage.sol"; +import {HostOrders} from "zenith/src/orders/HostOrders.sol"; +import {Passage} from "zenith/src/passage/Passage.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; /// @title PecorinoConstants @@ -16,6 +18,12 @@ library PecorinoConstants { /// @notice The Pecorino Rollup chain ID. uint32 constant ROLLUP_CHAIN_ID = 14174; + /// @notice The Passage contract for the Pecorino testnet host chain. + Passage constant HOST_PASSAGE = Passage(payable(0x12585352AA1057443D6163B539EfD4487f023182)); + + /// @notice The HostOrders contract for the Pecorino testnet host chain. + HostOrders constant HOST_ORDERS = HostOrders(0x0A4f505364De0Aa46c66b15aBae44eBa12ab0380); + /// @notice The Rollup Passage contract for the Pecorino testnet. RollupPassage constant PECORINO_ROLLUP_PASSAGE = RollupPassage(payable(0x0000000000007369676E65742D70617373616765)); diff --git a/src/interfaces/IWETH.sol b/src/interfaces/IWETH.sol new file mode 100644 index 0000000..1f3af1b --- /dev/null +++ b/src/interfaces/IWETH.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >0.8.13; + +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +interface IWETH is IERC20 { + /// @notice Deposit ETH to get WETH + function deposit() external payable; + + /// @notice Withdraw WETH to get ETH + function withdraw(uint256 amount) external; +} diff --git a/src/l1/Signet.sol b/src/l1/Signet.sol new file mode 100644 index 0000000..5377d5e --- /dev/null +++ b/src/l1/Signet.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {HostOrders} from "zenith/src/orders/HostOrders.sol"; +import {Passage} from "zenith/src/passage/Passage.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IWETH} from "../interfaces/IWETH.sol"; + +import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; +import {PecorinoConstants} from "../chains/Pecorino.sol"; + +abstract contract SignetL1 { + using SafeERC20 for IERC20; + + /// @notice Sentinel value for the native asset in order inputs/outputs + address constant NATIVE_ASSET = address(0); + + /// @notice The Passage address + Passage internal immutable PASSAGE; + /// @notice The Host Orders address + HostOrders internal immutable ORDERS; + + /// @notice The WETH token address. + IWETH internal immutable WETH; + /// @notice The WBTC token address. + IERC20 internal immutable WBTC; + /// @notice The USDC token address. + IERC20 internal immutable USDC; + /// @notice The USDT token address. + IERC20 internal immutable USDT; + + /// @notice The Rollup WUSD token address. + address internal immutable RU_WUSD; + /// @notice The Rollup WBTC token address. + address internal immutable RU_WBTC; + /// @notice The Rollup WETH token address. + address internal immutable RU_WETH; + + /// @notice Error for unsupported chain IDs. + error UnsupportedChain(uint256); + + constructor() { + if (block.chainid == PecorinoConstants.HOST_CHAIN_ID) { + PASSAGE = PecorinoConstants.HOST_PASSAGE; + ORDERS = PecorinoConstants.HOST_ORDERS; + + WETH = IWETH(PecorinoConstants.HOST_WETH); + WBTC = IERC20(PecorinoConstants.HOST_WBTC); + USDC = IERC20(PecorinoConstants.HOST_USDC); + USDT = IERC20(PecorinoConstants.HOST_USDT); + + RU_WUSD = address(PecorinoConstants.WUSD); + RU_WBTC = address(PecorinoConstants.WBTC); + RU_WETH = address(PecorinoConstants.WETH); + } else { + revert UnsupportedChain(block.chainid); + } + } + + /// @notice Returns the address of this contract on L2, applying an + /// address alias. + function selfOnL2() internal view virtual returns (address) { + address self = address(this); + if (self.code.length == 23) { + bool is7702; + + assembly { + let ptr := mload(0x40) + extcodecopy(self, ptr, 0, 0x20) + is7702 := eq(shr(232, mload(ptr)), 0xEF0100) + // clean the memory we used. Unnecessary, but good hygiene + mstore(ptr, 0x0) + } + if (is7702) { + return address(this); + } + } + return AddressAliasHelper.applyL1ToL2Alias(address(this)); + } + + /// @notice Helper to create an output struct. + function makeOutput(address token, uint256 amount, address recipient) + internal + pure + returns (HostOrders.Output memory output) + { + output.token = token; + output.amount = amount; + output.recipient = recipient; + output.chainId = PecorinoConstants.HOST_CHAIN_ID; + } + + /// @notice Helper to create an Output struct for usdc. + function usdcOutput(uint256 amount, address recipient) internal view returns (HostOrders.Output memory output) { + return makeOutput(address(USDC), amount, recipient); + } + + /// @notice Helper to create an Output struct for usdt. + function usdtOutput(uint256 amount, address recipient) internal view returns (HostOrders.Output memory output) { + return makeOutput(address(USDT), amount, recipient); + } + + /// @notice Helper to create an Output struct for wbtc. + function wbtcOutput(uint256 amount, address recipient) internal view returns (HostOrders.Output memory output) { + return makeOutput(address(WBTC), amount, recipient); + } + + /// @notice Helper to create an Output struct for weth. + function wethOutput(uint256 amount, address recipient) internal view returns (HostOrders.Output memory output) { + return makeOutput(address(WETH), amount, recipient); + } + + /// @notice Helper to create an Output struct for eth. + function ethOutput(uint256 amount, address recipient) internal pure returns (HostOrders.Output memory output) { + return makeOutput(NATIVE_ASSET, amount, recipient); + } + + /// @notice Send tokens into Signet via the Passage contract. + function tokensToSignet(address token, uint256 amount) internal { + if (token == NATIVE_ASSET) { + ethToSignet(amount); + return; + } + IERC20(token).forceApprove(address(PASSAGE), amount); + PASSAGE.enterToken(selfOnL2(), token, amount); + } + + /// @notice Send ETH into Signet via the Passage contract. + function ethToSignet(uint256 amount) internal { + PASSAGE.enter{value: amount}(selfOnL2()); + } + + /// @notice Send WETH into Signet via the Passage contract. + function wethToSignet(uint256 amount) internal { + WETH.withdraw(amount); + ethToSignet(amount); + } +} diff --git a/src/l1/SignetL1Ephemeral.sol b/src/l1/SignetL1Ephemeral.sol new file mode 100644 index 0000000..8f0dfa6 --- /dev/null +++ b/src/l1/SignetL1Ephemeral.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {SignetL1} from "./Signet.sol"; + +abstract contract SignetL1Ephemeral is SignetL1 { + function selfOnL2() internal view override returns (address) { + return address(this); + } +} diff --git a/src/l2/Signet.sol b/src/l2/Signet.sol index 458b380..4b37300 100644 --- a/src/l2/Signet.sol +++ b/src/l2/Signet.sol @@ -55,7 +55,7 @@ contract SignetL2 { HOST_WBTC = PecorinoConstants.HOST_WBTC; HOST_WETH = PecorinoConstants.HOST_WETH; } else { - revert("Unsupported chain"); + revert UnsupportedChain(block.chainid); } } diff --git a/src/vendor/AddressAliasHelper.sol b/src/vendor/AddressAliasHelper.sol new file mode 100644 index 0000000..7bcdbb9 --- /dev/null +++ b/src/vendor/AddressAliasHelper.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2019-2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pragma solidity ^0.8.0; + +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + unchecked { + l2Address = address(uint160(l1Address) + offset); + } + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + unchecked { + l1Address = address(uint160(l2Address) - offset); + } + } +} diff --git a/test/Tests.sol b/test/Tests.sol index 39b3aa2..9d2bf2b 100644 --- a/test/Tests.sol +++ b/test/Tests.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -contract TestNop { +import {Test} from "forge-std/Test.sol"; + +contract TestNop is Test { /// @notice Prevents foundry from complaining about no tests in CI. function test_nop() external pure returns (bool) { return true; } } +