Skip to content

Commit 6f55933

Browse files
authored
feat: basic third-party bridge app (#5)
* feat: basic third-party bridge app * lint: fmt * refactor: move down * chore: add some natspec * lint: fmt * chore: add natspec * refactor: use BurnMintErc20 everywhere * fix: ownable constructor invocation
1 parent 545d547 commit 6f55933

File tree

14 files changed

+430
-25
lines changed

14 files changed

+430
-25
lines changed

foundry.lock

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
{
2-
"lib/zenith": {
3-
"tag": {
4-
"name": "v0.1.55",
5-
"rev": "914146a3904541192f2cb0906c0990cc6f90b1e3"
6-
}
7-
},
82
"lib/forge-std": {
93
"tag": {
104
"name": "v1.10.0",
@@ -16,5 +10,14 @@
1610
"name": "v5.4.0",
1711
"rev": "c64a1edb67b6e3f4a15cca8909c9482ad33a02b0"
1812
}
13+
},
14+
"lib/simple-erc20": {
15+
"rev": "f5c2597ec179ea13e219ccbca415dc59d5a33398"
16+
},
17+
"lib/zenith": {
18+
"tag": {
19+
"name": "v0.1.55",
20+
"rev": "914146a3904541192f2cb0906c0990cc6f90b1e3"
21+
}
1922
}
2023
}

src/apps/Bridge.sol

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {RollupOrders} from "zenith/src/orders/RollupOrders.sol";
5+
import {BurnMintERC20} from "../vendor/BurnMintERC20.sol";
6+
7+
import {SignetL2} from "../l2/Signet.sol";
8+
9+
abstract contract BridgeL2 is SignetL2, BurnMintERC20 {
10+
/// @notice The address of the asset on the host chain.
11+
address immutable HOST_ASSET;
12+
/// @notice The address of the bank on the host chain. The bank holds the
13+
/// asset while tokens are bridged into the rollup.
14+
address immutable HOST_BANK;
15+
16+
constructor(address _hostAsset, address _hostBank, string memory _name, string memory _symbol, uint8 _decimals)
17+
BurnMintERC20(_name, _symbol, _decimals, 0, 0)
18+
{
19+
HOST_ASSET = _hostAsset;
20+
HOST_BANK = _hostBank;
21+
}
22+
23+
/// @notice Bridges assets into the rollup for a given recipient.
24+
function _bridgeIn(address recipient, uint256 amount, RollupOrders.Input[] memory inputs) internal virtual {
25+
_mint(recipient, amount);
26+
27+
RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
28+
outputs[0] = makeHostOutput(HOST_ASSET, amount, HOST_BANK);
29+
30+
ORDERS.initiate(block.timestamp, inputs, outputs);
31+
}
32+
33+
/// @notice Bridges assets into the rollup for a given recipient.
34+
function bridgeIn(address recipient, uint256 amount) public virtual {
35+
_bridgeIn(recipient, amount, new RollupOrders.Input[](0));
36+
}
37+
38+
/// @notice Burn asset on L2, and create an order to bridge out asset to
39+
/// L1. If the order is not filled, the asset will not be burned.
40+
///
41+
/// This transaction should be paired with some off-chain logic that fills
42+
/// orders from the L1 bank.
43+
function _bridgeOut(address sender, address recipient, uint256 amount, RollupOrders.Input[] memory inputs)
44+
internal
45+
virtual
46+
{
47+
if (_msgSender() != sender) {
48+
_spendAllowance(sender, _msgSender(), amount);
49+
}
50+
51+
_burn(msg.sender, amount);
52+
53+
RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
54+
outputs[0] = makeHostOutput(HOST_ASSET, amount, recipient);
55+
56+
ORDERS.initiate(block.timestamp, inputs, outputs);
57+
}
58+
59+
/// @notice Burn asset on L2, and create an order to bridge out assets to
60+
/// L1. If the order is not filled, the asset will not be burned.
61+
///
62+
/// This transaction should be paired with some off-chain logic that fills
63+
/// orders from the L1 bank.
64+
function bridgeOut(address recipient, uint256 amount) public virtual {
65+
_bridgeOut(msg.sender, recipient, amount, new RollupOrders.Input[](0));
66+
}
67+
68+
/// @notice Burn asset on L2 from `sender`, and create an order to bridge
69+
/// out assets to L1. If the order is not filled, the asset will not be
70+
/// burned. Used when the caller is not the sender.
71+
///
72+
/// This transaction should be paired with some off-chain logic that fills
73+
/// orders from the L1 bank.
74+
function bridgeOutFrom(address sender, address recipient, uint256 amount) public virtual {
75+
_bridgeOut(sender, recipient, amount, new RollupOrders.Input[](0));
76+
}
77+
}

src/apps/Lido.sol

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {RollupOrders} from "zenith/src/orders/RollupOrders.sol";
5+
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
6+
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
7+
8+
import {BridgeL2} from "./Bridge.sol";
9+
10+
/// @notice An example contract, implementing LIDO staking from Signet L2, with
11+
/// support for CCIP teleporting.
12+
/// Allows bridging two ways:
13+
/// - Signet native bridging with Orders.
14+
/// - CCIP Teleporting via support for the CCT standard.
15+
///
16+
/// Bridging with Signet:
17+
/// - bridgeIn: Creates an order that delilvers wstETH to `HOST_PASSAGE` on L1.
18+
/// If the order is filled, mints stETH on L2 to `recipient`.
19+
/// - bridgeOut: Burns stETH on L2 from `msg.sender`, and creates an order
20+
/// that delivers wstETH to `recipient` on L1.
21+
/// - enter: Transfers WETH from `funder`, creates an order that converts
22+
/// WETH to wstETH on L1 and delivers it to `HOST_PASSAGE`, and mints stETH
23+
/// on L2 to `recipient`.
24+
///
25+
contract LidoL2 is BridgeL2 {
26+
using SafeERC20 for IERC20;
27+
28+
/// @notice The WstETH token on the host.
29+
address public immutable HOST_WSTETH;
30+
31+
constructor(address _hostWsteth) BridgeL2(_hostWsteth, HOST_PASSAGE, "Lido Staked Ether", "stETH", 18) {
32+
HOST_WSTETH = _hostWsteth;
33+
WETH.forceApprove(address(ORDERS), type(uint256).max);
34+
}
35+
36+
/// @notice Transfer WETH from `funder`, create an order to convert it to
37+
/// wstETH on L1 and bridge it to L2, and mint stETH to `recipient`.
38+
function enter(uint256 amountIn, address recipient, uint256 amountOut) external {
39+
WETH.safeTransferFrom(msg.sender, address(this), amountIn);
40+
41+
RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1);
42+
inputs[0] = makeWethInput(amountIn);
43+
44+
_bridgeIn(recipient, amountOut, inputs);
45+
}
46+
}

src/apps/SignetCoreAsset.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {BridgeL2} from "./Bridge.sol";
5+
6+
contract SignetCoreAsset is BridgeL2 {
7+
constructor(
8+
address _hostAsset,
9+
address _hostPassageAdmin,
10+
string memory _name,
11+
string memory _symbol,
12+
uint8 _decimals
13+
) BridgeL2(_hostAsset, HOST_PASSAGE, _name, _symbol, _decimals) {
14+
_revokeRole(DEFAULT_ADMIN_ROLE, msg.sender);
15+
_grantRole(DEFAULT_ADMIN_ROLE, _hostPassageAdmin);
16+
_grantRole(MINTER_ROLE, TOKEN_MINTER);
17+
}
18+
}

src/chains/Pecorino.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ library PecorinoConstants {
2525
HostOrders constant HOST_ORDERS = HostOrders(0x0A4f505364De0Aa46c66b15aBae44eBa12ab0380);
2626

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

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

3333
/// USDC token for the Pecorino testnet host chain.
3434
address constant HOST_USDC = 0x65Fb255585458De1F9A246b476aa8d5C5516F6fd;

src/l2/SelfOwned.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
5+
6+
import {SignetL2} from "./Signet.sol";
7+
8+
abstract contract SelfOwned is SignetL2, Ownable {
9+
constructor() Ownable(aliasedSelf()) {}
10+
}

src/l2/Signet.sol

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import {RollupPassage} from "zenith/src/passage/RollupPassage.sol";
66
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
77

88
import {PecorinoConstants} from "../chains/Pecorino.sol";
9+
import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol";
910

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

15+
/// @notice System address that produces System minted tokens.
16+
address constant TOKEN_MINTER = 0x00000000000000000000746f6b656E61646d696E;
17+
1418
/// @notice The chain ID of the host network.
1519
uint32 internal immutable HOST_CHAIN_ID;
1620

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

26+
/// @notice The address of the Rollup Passage on the host network.
27+
address immutable HOST_PASSAGE;
28+
2229
/// @notice The WETH token address.
2330
IERC20 internal immutable WETH;
2431
/// @notice The WBTC token address.
@@ -43,8 +50,10 @@ contract SignetL2 {
4350
if (block.chainid == PecorinoConstants.ROLLUP_CHAIN_ID) {
4451
HOST_CHAIN_ID = PecorinoConstants.HOST_CHAIN_ID;
4552

46-
PASSAGE = PecorinoConstants.PECORINO_ROLLUP_PASSAGE;
47-
ORDERS = PecorinoConstants.PECORINO_ROLLUP_ORDERS;
53+
HOST_PASSAGE = address(PecorinoConstants.HOST_PASSAGE);
54+
55+
PASSAGE = PecorinoConstants.ROLLUP_PASSAGE;
56+
ORDERS = PecorinoConstants.ROLLUP_ORDERS;
4857

4958
WETH = PecorinoConstants.WETH;
5059
WBTC = PecorinoConstants.WBTC;
@@ -59,6 +68,12 @@ contract SignetL2 {
5968
}
6069
}
6170

71+
/// @notice Gets the aliased address of this contracat, representing itself
72+
/// on L1. Use with caustion.
73+
function aliasedSelf() internal view returns (address) {
74+
return AddressAliasHelper.applyL1ToL2Alias(address(this));
75+
}
76+
6277
/// @notice Creates an Input struct for the RollupOrders.
6378
/// @param token The address of the token.
6479
/// @param amount The amount of the token.
@@ -68,14 +83,24 @@ contract SignetL2 {
6883
input.amount = amount;
6984
}
7085

71-
/// @notice Creates an Input struct for the native asset (ETH).
86+
/// @notice Creates an Input struct for the native asset (USD).
7287
/// @param amount The amount of the native asset (in wei).
7388
/// @return input The created Input struct for the native asset.
74-
function makeEthInput(uint256 amount) internal pure returns (RollupOrders.Input memory input) {
89+
function makeUsdInput(uint256 amount) internal pure returns (RollupOrders.Input memory input) {
7590
input.token = address(0);
7691
input.amount = amount;
7792
}
7893

94+
function makeWethInput(uint256 amount) internal view returns (RollupOrders.Input memory input) {
95+
input.token = address(WETH);
96+
input.amount = amount;
97+
}
98+
99+
function makeWbtcInput(uint256 amount) internal view returns (RollupOrders.Input memory input) {
100+
input.token = address(WBTC);
101+
input.amount = amount;
102+
}
103+
79104
/// @notice Creates an Output struct for the RollupOrders.
80105
/// @param token The address of the token.
81106
/// @param amount The amount of the token.

src/l2/examples/GetOut.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ contract GetOut is SignetL2 {
3030
uint256 desired = msg.value * 995 / 1000; // 0.5% fee
3131

3232
RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1);
33-
inputs[0] = makeEthInput(msg.value);
33+
inputs[0] = makeUsdInput(msg.value);
3434

3535
RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
3636
outputs[0] = hostUsdcOutput(desired, msg.sender);

0 commit comments

Comments
 (0)