diff --git a/foundry.toml b/foundry.toml index 06191ee..7d4a7dc 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,3 +6,7 @@ libs = ["lib"] via-ir = true # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + + +[lint] +exclude_lints = ["unwrapped-modifier-logic"] \ No newline at end of file diff --git a/src/apps/Morpho.sol b/src/apps/Morpho.sol index 401fbc1..fb1e653 100644 --- a/src/apps/Morpho.sol +++ b/src/apps/Morpho.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.13; import {IMorpho, MarketParams} from "../interfaces/IMorpho.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {SignetStd} from "../SignetStd.sol"; +import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SignetL2} from "../l2/Signet.sol"; import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; -import {Passage} from "zenith/src/passage/Passage.sol"; // How this works: // - The Signet Orders system calls `transferFrom` and check for the presence @@ -38,68 +38,70 @@ import {Passage} from "zenith/src/passage/Passage.sol"; // The Rollup contract creates orders to interact with Morpho on host net via // the shortcut. -contract UseMorpho is SignetStd { +contract UseMorpho is SignetL2 { + using SafeERC20 for IERC20; + /// @dev Address of the shortcut on the host chain. - address immutable repayShortcut; - address immutable supplyShortcut; - address immutable borrowShortcut; + address immutable REPAY_SHORTCUT; + address immutable SUPPLY_SHORTCUT; + address immutable BORROW_SHORTCUT; - address immutable hostLoanToken; - address immutable hostCollateralToken; + address immutable HOST_LOAN_TOKEN; + address immutable HOST_COLLATERAL_TOKEN; - IERC20 immutable ruLoanToken; - IERC20 immutable ruCollateralToken; + IERC20 immutable RU_LOAN_TOKEN; + IERC20 immutable RU_COLLATERAL_TOKEN; constructor(address _repayShortcut, address _supplyShortcut, address _hostLoan, address _hostCollateral) - SignetStd() + SignetL2() { - repayShortcut = _repayShortcut; - supplyShortcut = _supplyShortcut; + REPAY_SHORTCUT = _repayShortcut; + SUPPLY_SHORTCUT = _supplyShortcut; // Autodetect rollup tokens based on token addresses on host network. - hostLoanToken = _hostLoan; - hostCollateralToken = _hostCollateral; + HOST_LOAN_TOKEN = _hostLoan; + HOST_COLLATERAL_TOKEN = _hostCollateral; if (_hostLoan == HOST_WETH) { - ruLoanToken = WETH; + RU_LOAN_TOKEN = WETH; } else if (_hostLoan == HOST_WBTC) { - ruLoanToken = WBTC; + RU_LOAN_TOKEN = WBTC; } else if (_hostLoan == HOST_USDC || _hostLoan == HOST_USDT) { - ruLoanToken = WUSD; + RU_LOAN_TOKEN = WUSD; } else { revert("Unsupported loan token"); } if (_hostCollateral == HOST_WETH) { - ruCollateralToken = WETH; + RU_COLLATERAL_TOKEN = WETH; } else if (_hostCollateral == HOST_WBTC) { - ruCollateralToken = WBTC; + RU_COLLATERAL_TOKEN = WBTC; } else if (_hostCollateral == HOST_USDC || _hostCollateral == HOST_USDT) { - ruCollateralToken = WUSD; + RU_COLLATERAL_TOKEN = WUSD; } else { revert("Unsupported collateral token"); } // Pre-emptively approve the Orders contract to spend our tokens. - ruLoanToken.approve(address(ORDERS), type(uint256).max); - ruCollateralToken.approve(address(ORDERS), type(uint256).max); + RU_LOAN_TOKEN.approve(address(ORDERS), type(uint256).max); + RU_COLLATERAL_TOKEN.approve(address(ORDERS), type(uint256).max); } // Supply some amount of the collateral token on behalf of the user. function supplyCollateral(address onBehalf, uint256 amount) public { if (amount > 0) { - ruCollateralToken.transferFrom(msg.sender, address(this), amount); + RU_COLLATERAL_TOKEN.safeTransferFrom(msg.sender, address(this), amount); } // the amount is whatever our current balance is - amount = ruCollateralToken.balanceOf(address(this)); + amount = RU_COLLATERAL_TOKEN.balanceOf(address(this)); RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1); - inputs[0] = makeInput(address(ruCollateralToken), amount); + inputs[0] = makeInput(address(RU_COLLATERAL_TOKEN), amount); // The first output pays the collateral token to the shortcut. // The second output calls the shortcut to supply the collateral. RollupOrders.Output[] memory outputs = new RollupOrders.Output[](2); - outputs[0] = makeHostOutput(address(hostCollateralToken), amount, supplyShortcut); - outputs[1] = makeHostOutput(supplyShortcut, amount, onBehalf); + outputs[0] = makeHostOutput(address(HOST_COLLATERAL_TOKEN), amount, SUPPLY_SHORTCUT); + outputs[1] = makeHostOutput(SUPPLY_SHORTCUT, amount, onBehalf); ORDERS.initiate( block.timestamp, // no deadline @@ -111,20 +113,20 @@ contract UseMorpho is SignetStd { // Repay some amount of the loan token on behalf of the user. function repay(address onBehalf, uint256 amount) public { if (amount > 0) { - ruLoanToken.transferFrom(msg.sender, address(this), amount); + RU_LOAN_TOKEN.safeTransferFrom(msg.sender, address(this), amount); } // Send all tokens. - amount = ruLoanToken.balanceOf(address(this)); + amount = RU_LOAN_TOKEN.balanceOf(address(this)); RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1); - inputs[0] = makeInput(address(ruLoanToken), amount); + inputs[0] = makeInput(address(RU_LOAN_TOKEN), amount); // The first output pays the loan token to the shortcut. // The second output calls the shortcut to repay the loan. RollupOrders.Output[] memory outputs = new RollupOrders.Output[](2); - outputs[0] = makeHostOutput(address(hostLoanToken), amount, repayShortcut); - outputs[1] = makeHostOutput(repayShortcut, amount, onBehalf); + outputs[0] = makeHostOutput(address(HOST_LOAN_TOKEN), amount, REPAY_SHORTCUT); + outputs[1] = makeHostOutput(REPAY_SHORTCUT, amount, onBehalf); ORDERS.initiate( block.timestamp, // no deadline @@ -140,8 +142,8 @@ contract UseMorpho is SignetStd { // The first output calls the shortcut to borrow the loan. // The second output sends the loan token to the user on the rollup. RollupOrders.Output[] memory outputs = new RollupOrders.Output[](2); - outputs[0] = makeHostOutput(borrowShortcut, amount, onBehalf); - outputs[1] = makeRollupOutput(address(ruLoanToken), amount, msg.sender); + outputs[0] = makeHostOutput(BORROW_SHORTCUT, amount, onBehalf); + outputs[1] = makeRollupOutput(address(RU_LOAN_TOKEN), amount, msg.sender); ORDERS.initiate( block.timestamp, // no deadline @@ -160,29 +162,29 @@ contract UseMorpho is SignetStd { } abstract contract HostMorphoUser { - IMorpho immutable morpho; + IMorpho immutable MORPHO; // This is an unrolled MarketParams struct. - IERC20 immutable loanToken; - IERC20 immutable collateralToken; + IERC20 immutable LOAN_TOKEN; + IERC20 immutable COLLATERAL_TOKEN; - address immutable oracle; - address immutable irm; - uint256 immutable lltv; + address immutable ORACLE; + address immutable IRM; + uint256 immutable LLTV; error InsufficentTokensReceived(uint256 received, uint256 required); constructor(IMorpho _morpho, MarketParams memory _params) { - morpho = _morpho; + MORPHO = _morpho; - loanToken = IERC20(_params.loanToken); - collateralToken = IERC20(_params.collateralToken); - oracle = _params.oracle; - irm = _params.irm; - lltv = _params.lltv; + LOAN_TOKEN = IERC20(_params.loanToken); + COLLATERAL_TOKEN = IERC20(_params.collateralToken); + ORACLE = _params.oracle; + IRM = _params.irm; + LLTV = _params.lltv; - loanToken.approve(address(_morpho), type(uint256).max); - collateralToken.approve(address(_morpho), type(uint256).max); + LOAN_TOKEN.approve(address(_morpho), type(uint256).max); + COLLATERAL_TOKEN.approve(address(_morpho), type(uint256).max); } function checkReceived(uint256 received, uint256 required) internal pure { @@ -192,11 +194,11 @@ abstract contract HostMorphoUser { } function loadParams() internal view returns (MarketParams memory params) { - params.loanToken = address(loanToken); - params.collateralToken = address(collateralToken); - params.oracle = oracle; - params.irm = irm; - params.lltv = lltv; + params.loanToken = address(LOAN_TOKEN); + params.collateralToken = address(COLLATERAL_TOKEN); + params.oracle = ORACLE; + params.irm = IRM; + params.lltv = LLTV; } } @@ -208,9 +210,9 @@ contract HostMorphoRepay is HostMorphoUser { /// Uses the ERC20 transferFrom interface to invoke contract logic. This /// allows us to invoke logic from the Orders contract function transferFrom(address, address recipient, uint256 amount) external returns (bool) { - uint256 loanTokenBalance = loanToken.balanceOf(address(this)); + uint256 loanTokenBalance = LOAN_TOKEN.balanceOf(address(this)); checkReceived(loanTokenBalance, amount); - morpho.repay(loadParams(), loanTokenBalance, 0, recipient, ""); + MORPHO.repay(loadParams(), loanTokenBalance, 0, recipient, ""); return true; } } @@ -221,11 +223,11 @@ contract HostMorphoSupply is HostMorphoUser { /// Uses the ERC20 transferFrom interface to invoke contract logic. This /// allows us to invoke logic from the Orders contract function transferFrom(address, address recipient, uint256 amount) external returns (bool) { - uint256 collateralTokenBalance = collateralToken.balanceOf(address(this)); + uint256 collateralTokenBalance = COLLATERAL_TOKEN.balanceOf(address(this)); checkReceived(collateralTokenBalance, amount); - morpho.supplyCollateral(loadParams(), collateralTokenBalance, recipient, ""); + MORPHO.supplyCollateral(loadParams(), collateralTokenBalance, recipient, ""); // Future extension: // borrow some amount of loanToken @@ -245,7 +247,7 @@ contract HosyMorphoBorrow is HostMorphoUser { function transferFrom(address filler, address onBehalf, uint256 amount) external returns (bool) { // borrow some amount of loanToken - morpho.borrow(loadParams(), amount, 0, onBehalf, address(this)); + MORPHO.borrow(loadParams(), amount, 0, onBehalf, address(this)); // User logic to use the tokens goes here. // Could send the tokens to the rollup via Passage, or do something diff --git a/src/SignetStd.sol b/src/l2/Signet.sol similarity index 89% rename from src/SignetStd.sol rename to src/l2/Signet.sol index 7be5efa..458b380 100644 --- a/src/SignetStd.sol +++ b/src/l2/Signet.sol @@ -5,12 +5,11 @@ import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; import {RollupPassage} from "zenith/src/passage/RollupPassage.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {PecorinoConstants} from "./chains/Pecorino.sol"; +import {PecorinoConstants} from "../chains/Pecorino.sol"; -contract SignetStd { - /// @notice The native asset address, used as a sentinel for native USD on - /// the rollup, or native ETH on the host. - address constant NATIVE_ASSET = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; +contract SignetL2 { + /// @notice Sentinal value for the native asset in order inputs/outputs + address constant NATIVE_ASSET = address(0); /// @notice The chain ID of the host network. uint32 internal immutable HOST_CHAIN_ID; @@ -36,6 +35,9 @@ contract SignetStd { /// @notice The WETH token address on the host network. address internal immutable HOST_WETH; + /// @notice Error for unsupported chain IDs. + error UnsupportedChain(uint256); + constructor() { // Auto-configure based on the chain ID. if (block.chainid == PecorinoConstants.ROLLUP_CHAIN_ID) { @@ -66,6 +68,14 @@ contract SignetStd { input.amount = amount; } + /// @notice Creates an Input struct for the native asset (ETH). + /// @param amount The amount of the native asset (in wei). + /// @return input The created Input struct for the native asset. + function makeEthInput(uint256 amount) internal pure returns (RollupOrders.Input memory input) { + input.token = address(0); + input.amount = amount; + } + /// @notice Creates an Output struct for the RollupOrders. /// @param token The address of the token. /// @param amount The amount of the token. @@ -166,6 +176,6 @@ contract SignetStd { view returns (RollupOrders.Output memory output) { - return makeHostOutput(NATIVE_ASSET, amount, recipient); + return makeHostOutput(address(0), amount, recipient); } } diff --git a/src/examples/Flash.sol b/src/l2/examples/Flash.sol similarity index 88% rename from src/examples/Flash.sol rename to src/l2/examples/Flash.sol index deb85f7..136b5ea 100644 --- a/src/examples/Flash.sol +++ b/src/l2/examples/Flash.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.13; import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; -import {SignetStd} from "../SignetStd.sol"; +import {SignetL2} from "../Signet.sol"; /// @notice This contract provides a modifier that allows functions to /// utilize liquidity only during the duration of a function. -abstract contract Flash is SignetStd { +abstract contract Flash is SignetL2 { /// @notice This modifier enables a contract to access some amount only /// during function execution - the amount is received by the /// contract before the function executes, then is sent directly @@ -18,7 +18,10 @@ abstract contract Flash is SignetStd { /// @param amount The amount of the asset to be flash held. modifier flash(address asset, uint256 amount) { _; + _flash(asset, amount); + } + function _flash(address asset, uint256 amount) internal { // Output is received *before* the modified function is called RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1); outputs[0] = makeRollupOutput(asset, amount, address(this)); diff --git a/src/examples/GetOut.sol b/src/l2/examples/GetOut.sol similarity index 91% rename from src/examples/GetOut.sol rename to src/l2/examples/GetOut.sol index ef7be3c..3544cd7 100644 --- a/src/examples/GetOut.sol +++ b/src/l2/examples/GetOut.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; -import {SignetStd} from "../SignetStd.sol"; +import {SignetL2} from "../Signet.sol"; /// @title GetOut /// @author init4 @@ -10,7 +10,7 @@ import {SignetStd} from "../SignetStd.sol"; /// to USDC on the host network. /// @dev This contract inherits the SignetStd contract and automatically /// configures rollup constants on construction. -contract GetOut is SignetStd { +contract GetOut is SignetL2 { /// @notice Thrown when no value is sent to the contract. error MissingValue(); @@ -30,7 +30,7 @@ contract GetOut is SignetStd { uint256 desired = msg.value * 995 / 1000; // 0.5% fee RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1); - inputs[0] = makeInput(NATIVE_ASSET, msg.value); + inputs[0] = makeEthInput(msg.value); RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1); outputs[0] = hostUsdcOutput(desired, msg.sender); diff --git a/src/examples/PayMe.sol b/src/l2/examples/PayMe.sol similarity index 89% rename from src/examples/PayMe.sol rename to src/l2/examples/PayMe.sol index 509d572..f5af219 100644 --- a/src/examples/PayMe.sol +++ b/src/l2/examples/PayMe.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.13; import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; -import {SignetStd} from "../SignetStd.sol"; +import {SignetL2} from "../Signet.sol"; /// @notice This contract provides a modifier that allows functions to be gated /// by requiring a payment of a specified amount of native asset. -abstract contract PayMe is SignetStd { +abstract contract PayMe is SignetL2 { /// @notice This modifier crates an order with no input, that pays the /// specified amount of native asset to the contract. It can be used /// to gate access to payment-gate functions. @@ -19,9 +19,14 @@ abstract contract PayMe is SignetStd { /// transaction by deducting it from the payment amount. modifier payMeSubsidizedGas(uint256 amount) { uint256 pre = gasleft(); - uint256 gp = tx.gasprice; _; + _payMeSubsidizedGasAfter(pre, amount); + } + + /// @notice This silences spurious foundry warnings. + function _payMeSubsidizedGasAfter(uint256 pre, uint256 amount) internal { uint256 post = gasleft(); + uint256 gp = tx.gasprice; uint256 loot = amount - (gp * (pre - post)); demandPayment(NATIVE_ASSET, loot); } diff --git a/src/examples/PayYou.sol b/src/l2/examples/PayYou.sol similarity index 84% rename from src/examples/PayYou.sol rename to src/l2/examples/PayYou.sol index 59cab85..2fadafc 100644 --- a/src/examples/PayYou.sol +++ b/src/l2/examples/PayYou.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.13; import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; -import {SignetStd} from "../SignetStd.sol"; +import {SignetL2} from "../Signet.sol"; /// @notice This contract provides tools for contracts to pay searchers to run /// code. -abstract contract PayYou is SignetStd { +abstract contract PayYou is SignetL2 { /// @notice A modifier that creates USD MEV of the specified amount. modifier paysYou(uint256 tip) { _; @@ -17,8 +17,13 @@ abstract contract PayYou is SignetStd { /// function modified. modifier paysYourGas(uint256 tip) { uint256 pre = gasleft(); - uint256 gp = tx.gasprice; _; + _paysYourGasAfter(pre, tip); + } + + /// @notice This silences spurious foundry warnings. + function _paysYourGasAfter(uint256 pre, uint256 tip) internal { + uint256 gp = tx.gasprice; uint256 post = gasleft(); uint256 loot = tip + (gp * (pre - post)); providePayment(NATIVE_ASSET, loot);