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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/zenith"]
path = lib/zenith
url = https://github.com/init4tech/zenith
[submodule "lib/simple-erc20"]
path = lib/simple-erc20
url = https://github.com/init4tech/simple-erc20
15 changes: 9 additions & 6 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
{
"lib/zenith": {
"tag": {
"name": "v0.1.55",
"rev": "914146a3904541192f2cb0906c0990cc6f90b1e3"
}
},
"lib/forge-std": {
"tag": {
"name": "v1.10.0",
Expand All @@ -16,5 +10,14 @@
"name": "v5.4.0",
"rev": "c64a1edb67b6e3f4a15cca8909c9482ad33a02b0"
}
},
"lib/simple-erc20": {
"rev": "f5c2597ec179ea13e219ccbca415dc59d5a33398"
},
"lib/zenith": {
"tag": {
"name": "v0.1.55",
"rev": "914146a3904541192f2cb0906c0990cc6f90b1e3"
}
}
}
1 change: 1 addition & 0 deletions lib/simple-erc20
Submodule simple-erc20 added at f5c259
77 changes: 77 additions & 0 deletions src/apps/Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {RollupOrders} from "zenith/src/orders/RollupOrders.sol";
import {BurnMintERC20} from "../vendor/BurnMintERC20.sol";

import {SignetL2} from "../l2/Signet.sol";

abstract contract BridgeL2 is SignetL2, BurnMintERC20 {
/// @notice The address of the asset on the host chain.
address immutable HOST_ASSET;
/// @notice The address of the bank on the host chain. The bank holds the
/// asset while tokens are bridged into the rollup.
address immutable HOST_BANK;

constructor(address _hostAsset, address _hostBank, string memory _name, string memory _symbol, uint8 _decimals)
BurnMintERC20(_name, _symbol, _decimals, 0, 0)
{
HOST_ASSET = _hostAsset;
HOST_BANK = _hostBank;
}

/// @notice Bridges assets into the rollup for a given recipient.
function _bridgeIn(address recipient, uint256 amount, RollupOrders.Input[] memory inputs) internal virtual {
_mint(recipient, amount);

RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
outputs[0] = makeHostOutput(HOST_ASSET, amount, HOST_BANK);

ORDERS.initiate(block.timestamp, inputs, outputs);
}

/// @notice Bridges assets into the rollup for a given recipient.
function bridgeIn(address recipient, uint256 amount) public virtual {
_bridgeIn(recipient, amount, new RollupOrders.Input[](0));
}

/// @notice Burn asset on L2, and create an order to bridge out asset to
/// L1. If the order is not filled, the asset will not be burned.
///
/// This transaction should be paired with some off-chain logic that fills
/// orders from the L1 bank.
function _bridgeOut(address sender, address recipient, uint256 amount, RollupOrders.Input[] memory inputs)
internal
virtual
{
if (_msgSender() != sender) {
_spendAllowance(sender, _msgSender(), amount);
}

_burn(msg.sender, amount);

RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
outputs[0] = makeHostOutput(HOST_ASSET, amount, recipient);

ORDERS.initiate(block.timestamp, inputs, outputs);
}

/// @notice Burn asset on L2, and create an order to bridge out assets to
/// L1. If the order is not filled, the asset will not be burned.
///
/// This transaction should be paired with some off-chain logic that fills
/// orders from the L1 bank.
function bridgeOut(address recipient, uint256 amount) public virtual {
_bridgeOut(msg.sender, recipient, amount, new RollupOrders.Input[](0));
}

/// @notice Burn asset on L2 from `sender`, and create an order to bridge
/// out assets to L1. If the order is not filled, the asset will not be
/// burned. Used when the caller is not the sender.
///
/// This transaction should be paired with some off-chain logic that fills
/// orders from the L1 bank.
function bridgeOutFrom(address sender, address recipient, uint256 amount) public virtual {
_bridgeOut(sender, recipient, amount, new RollupOrders.Input[](0));
}
}
46 changes: 46 additions & 0 deletions src/apps/Lido.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {RollupOrders} from "zenith/src/orders/RollupOrders.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

import {BridgeL2} from "./Bridge.sol";

/// @notice An example contract, implementing LIDO staking from Signet L2, with
/// support for CCIP teleporting.
/// Allows bridging two ways:
/// - Signet native bridging with Orders.
/// - CCIP Teleporting via support for the CCT standard.
///
/// Bridging with Signet:
/// - bridgeIn: Creates an order that delilvers wstETH to `HOST_PASSAGE` on L1.
/// If the order is filled, mints stETH on L2 to `recipient`.
/// - bridgeOut: Burns stETH on L2 from `msg.sender`, and creates an order
/// that delivers wstETH to `recipient` on L1.
/// - enter: Transfers WETH from `funder`, creates an order that converts
/// WETH to wstETH on L1 and delivers it to `HOST_PASSAGE`, and mints stETH
/// on L2 to `recipient`.
///
contract LidoL2 is BridgeL2 {
using SafeERC20 for IERC20;

/// @notice The WstETH token on the host.
address public immutable HOST_WSTETH;

constructor(address _hostWsteth) BridgeL2(_hostWsteth, HOST_PASSAGE, "Lido Staked Ether", "stETH", 18) {
HOST_WSTETH = _hostWsteth;
WETH.forceApprove(address(ORDERS), type(uint256).max);
}

/// @notice Transfer WETH from `funder`, create an order to convert it to
/// wstETH on L1 and bridge it to L2, and mint stETH to `recipient`.
function enter(uint256 amountIn, address recipient, uint256 amountOut) external {
WETH.safeTransferFrom(msg.sender, address(this), amountIn);

RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1);
inputs[0] = makeWethInput(amountIn);

_bridgeIn(recipient, amountOut, inputs);
}
}
18 changes: 18 additions & 0 deletions src/apps/SignetCoreAsset.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {BridgeL2} from "./Bridge.sol";

contract SignetCoreAsset is BridgeL2 {
constructor(
address _hostAsset,
address _hostPassageAdmin,
string memory _name,
string memory _symbol,
uint8 _decimals
) BridgeL2(_hostAsset, HOST_PASSAGE, _name, _symbol, _decimals) {
_revokeRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(DEFAULT_ADMIN_ROLE, _hostPassageAdmin);
_grantRole(MINTER_ROLE, TOKEN_MINTER);
}
}
4 changes: 2 additions & 2 deletions src/chains/Pecorino.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ library PecorinoConstants {
HostOrders constant HOST_ORDERS = HostOrders(0x0A4f505364De0Aa46c66b15aBae44eBa12ab0380);

/// @notice The Rollup Passage contract for the Pecorino testnet.
RollupPassage constant PECORINO_ROLLUP_PASSAGE = RollupPassage(payable(0x0000000000007369676E65742D70617373616765));
RollupPassage constant ROLLUP_PASSAGE = RollupPassage(payable(0x0000000000007369676E65742D70617373616765));

/// @notice The Rollup Orders contract for the Pecorino testnet.
RollupOrders constant PECORINO_ROLLUP_ORDERS = RollupOrders(0x000000000000007369676E65742D6f7264657273);
RollupOrders constant ROLLUP_ORDERS = RollupOrders(0x000000000000007369676E65742D6f7264657273);

/// USDC token for the Pecorino testnet host chain.
address constant HOST_USDC = 0x65Fb255585458De1F9A246b476aa8d5C5516F6fd;
Expand Down
10 changes: 10 additions & 0 deletions src/l2/SelfOwned.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";

import {SignetL2} from "./Signet.sol";

abstract contract SelfOwned is SignetL2, Ownable {
constructor() Ownable(aliasedSelf()) {}
}
33 changes: 29 additions & 4 deletions src/l2/Signet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ 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 {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol";

contract SignetL2 {
/// @notice Sentinal value for the native asset in order inputs/outputs
address constant NATIVE_ASSET = address(0);

/// @notice System address that produces System minted tokens.
address constant TOKEN_MINTER = 0x00000000000000000000746f6b656E61646d696E;

/// @notice The chain ID of the host network.
uint32 internal immutable HOST_CHAIN_ID;

Expand All @@ -19,6 +23,9 @@ contract SignetL2 {
/// @notice The Rollup Orders contract.
RollupOrders internal immutable ORDERS;

/// @notice The address of the Rollup Passage on the host network.
address immutable HOST_PASSAGE;

/// @notice The WETH token address.
IERC20 internal immutable WETH;
/// @notice The WBTC token address.
Expand All @@ -43,8 +50,10 @@ contract SignetL2 {
if (block.chainid == PecorinoConstants.ROLLUP_CHAIN_ID) {
HOST_CHAIN_ID = PecorinoConstants.HOST_CHAIN_ID;

PASSAGE = PecorinoConstants.PECORINO_ROLLUP_PASSAGE;
ORDERS = PecorinoConstants.PECORINO_ROLLUP_ORDERS;
HOST_PASSAGE = address(PecorinoConstants.HOST_PASSAGE);

PASSAGE = PecorinoConstants.ROLLUP_PASSAGE;
ORDERS = PecorinoConstants.ROLLUP_ORDERS;

WETH = PecorinoConstants.WETH;
WBTC = PecorinoConstants.WBTC;
Expand All @@ -59,6 +68,12 @@ contract SignetL2 {
}
}

/// @notice Gets the aliased address of this contracat, representing itself
/// on L1. Use with caustion.
function aliasedSelf() internal view returns (address) {
return AddressAliasHelper.applyL1ToL2Alias(address(this));
}

/// @notice Creates an Input struct for the RollupOrders.
/// @param token The address of the token.
/// @param amount The amount of the token.
Expand All @@ -68,14 +83,24 @@ contract SignetL2 {
input.amount = amount;
}

/// @notice Creates an Input struct for the native asset (ETH).
/// @notice Creates an Input struct for the native asset (USD).
/// @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) {
function makeUsdInput(uint256 amount) internal pure returns (RollupOrders.Input memory input) {
input.token = address(0);
input.amount = amount;
}

function makeWethInput(uint256 amount) internal view returns (RollupOrders.Input memory input) {
input.token = address(WETH);
input.amount = amount;
}

function makeWbtcInput(uint256 amount) internal view returns (RollupOrders.Input memory input) {
input.token = address(WBTC);
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
2 changes: 1 addition & 1 deletion src/l2/examples/GetOut.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract GetOut is SignetL2 {
uint256 desired = msg.value * 995 / 1000; // 0.5% fee

RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1);
inputs[0] = makeEthInput(msg.value);
inputs[0] = makeUsdInput(msg.value);

RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
outputs[0] = hostUsdcOutput(desired, msg.sender);
Expand Down
Loading