Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
120 changes: 61 additions & 59 deletions src/apps/Morpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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;
}
}

Expand All @@ -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;
}
}
Expand All @@ -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
Expand All @@ -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
Expand Down
22 changes: 16 additions & 6 deletions src/SignetStd.sol → src/l2/Signet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -166,6 +176,6 @@ contract SignetStd {
view
returns (RollupOrders.Output memory output)
{
return makeHostOutput(NATIVE_ASSET, amount, recipient);
return makeHostOutput(address(0), amount, recipient);
}
}
7 changes: 5 additions & 2 deletions src/examples/Flash.sol → src/l2/examples/Flash.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
Expand Down
6 changes: 3 additions & 3 deletions src/examples/GetOut.sol → src/l2/examples/GetOut.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
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
/// @notice A contract that gets out of the Rollup by converting native USD to
/// 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();

Expand All @@ -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);
Expand Down
11 changes: 8 additions & 3 deletions src/examples/PayMe.sol → src/l2/examples/PayMe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}
Expand Down
Loading