Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build: router #16

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
35 changes: 35 additions & 0 deletions src/router/IPermit2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.20;

interface IPermit2 {
struct PermitTransferFrom {
TokenPermissions permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}

struct TokenPermissions {
// ERC20 token address
address token;
// the maximum amount that can be spent
uint256 amount;
}

struct SignatureTransferDetails {
// recipient address
address to;
// spender requested amount
uint256 requestedAmount;
}

function permitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes calldata signature
) external;

function DOMAIN_SEPARATOR() external view returns (bytes32);
}
34 changes: 34 additions & 0 deletions src/router/Multicall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an exact copy?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think so

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guess i removed the interface andd added the comment to the function


// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.6;

/// @title Multicall
/// @notice Enables calling multiple methods in a single call to the contract
abstract contract Multicall {
/// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
/// @dev The `msg.value` should not be trusted for any method callable from multicall.
/// @param data The encoded function data for each of the calls to make to this contract
/// @return results The results from each of the calls passed in via data
function multicall(
bytes[] calldata data
) public payable returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(
data[i]
);

if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert();
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}

results[i] = result;
}
}
}
98 changes: 98 additions & 0 deletions src/router/PeripheryPayments.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
@title Periphery Payments
@notice Immutable state used by periphery contracts
Largely Forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/PeripheryPayments.sol
Changes:
* no interface
* no inheritdoc
* add immutable WETH9 in constructor instead of PeripheryImmutableState
* receive from any address
* Solmate interfaces and transfer lib
* casting
* add approve, wrapWETH9 and pullToken
*/
abstract contract PeripheryPayments {
using SafeERC20 for *;

IWETH9 public immutable WETH9;

constructor(address _WETH9) {
WETH9 = IWETH9(_WETH9);
}

receive() external payable {}

function approve(ERC20 token, address to, uint256 amount) public payable {
token.forceApprove(to, amount);
}

function unwrapWETH9(
uint256 amountMinimum,
address recipient
) public payable {
uint256 balanceWETH9 = WETH9.balanceOf(address(this));
require(balanceWETH9 >= amountMinimum, "Insufficient WETH9");

if (balanceWETH9 > 0) {
WETH9.withdraw(balanceWETH9);
safeTransferETH(recipient, balanceWETH9);
}
}

function wrapWETH9() public payable {
if (address(this).balance > 0)
WETH9.deposit{value: address(this).balance}(); // wrap everything
}

function pullToken(
ERC20 token,
uint256 amount,
address recipient
) public payable {
token.safeTransferFrom(msg.sender, recipient, amount);
}

function sweepToken(
ERC20 token,
uint256 amountMinimum,
address recipient
) public payable {
uint256 balanceToken = token.balanceOf(address(this));
require(balanceToken >= amountMinimum, "Insufficient token");

if (balanceToken > 0) {
token.safeTransfer(recipient, balanceToken);
}
}

function refundETH() external payable {
if (address(this).balance > 0)
safeTransferETH(msg.sender, address(this).balance);
}

// From https://github.com/transmissions11/solmate/blob/9f16db2144cc9a7e2ffc5588d4bf0b66784283bd/src/utils/SafeTransferLib.sol
function safeTransferETH(address to, uint256 amount) internal {
bool success;

assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}

require(success, "ETH_TRANSFER_FAILED");
}
}

abstract contract IWETH9 is ERC20 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this an abstract contract instead of an interface?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already had the ERC20 dependency i guess

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW these two contracts were just basically copied from the 4626 Router that was forked from FEI https://github.com/yearn/Yearn-ERC4626-Router/tree/master/src/external

/// @notice Deposit ether to get wrapped ether
function deposit() external payable virtual;

/// @notice Withdraw wrapped ether to get ether
function withdraw(uint256) external virtual;
}
176 changes: 176 additions & 0 deletions src/router/STBRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.20;

import {L1YearnEscrow} from "../L1YearnEscrow.sol";
import {L1Deployer} from "../L1Deployer.sol";

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {Multicall} from "./Multicall.sol";
import {PeripheryPayments} from "./PeripheryPayments.sol";

import {IPermit2} from "./IPermit2.sol";

/// @notice Simple router for bridging to any L2's that use the LxLy Stake the Bridge implementation.
/// @dev Allows for multicall, native ETH and Permit2 bridging.
contract STBRouter is Multicall, PeripheryPayments {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm feeling dumb ... isn't PeripheryPayments completely open to all callers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. Any tokens left in the router after a txn can get pulled by anyone.

The idea is the multicall allows any of them to be used through a delegatecall

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you know if the rationale for Uniswap was to not keep internal accounting, and thus simplify the implementation?

using SafeERC20 for ERC20;

/// @notice The canonical permit2 contract.
IPermit2 public immutable PERMIT2;

/// @notice The L1 Deployer for Polygon's LxLy Escrows.
L1Deployer public immutable L1DEPLOYER;

constructor(
address weth,
address _permit2,
address _l1Deployer
) PeripheryPayments(weth) {
PERMIT2 = IPermit2(_permit2);
L1DEPLOYER = L1Deployer(_l1Deployer);
}

/**
* @notice Name of the contract
*/
function name() external pure returns (string memory) {
return "Stake The Bridge Router";
}

/**
* @notice Bridge all of token to a L2.
* @dev Default to msg sender for receiver and the full balance as amount.
* @param _rollupID The Polygon Rollup ID to bridge to.
* @param _asset Token to bridge.
*/
function bridge(uint32 _rollupID, address _asset) external virtual {
_bridge(
_rollupID,
_asset,
ERC20(_asset).balanceOf(msg.sender),
msg.sender
);
}

/**
* @notice Bridge a token to a L2.
* @dev Defaults to msg.sender as the receiver.
* @param _rollupID The Polygon Rollup ID to bridge to.
* @param _asset Token to bridge.
* @param _amount The amount of `_asset` to bridge.
*/
function bridge(
uint32 _rollupID,
address _asset,
uint256 _amount
) external virtual {
_bridge(_rollupID, _asset, _amount, msg.sender);
}

/**
* @notice Bridge a token to a L2.
* @param _rollupID The Polygon Rollup ID to bridge to.
* @param _asset Token to bridge.
* @param _amount The amount of `_asset` to bridge.
* @param _receiver The address to receive the tokens on the L2.
*/
function bridge(
uint32 _rollupID,
address _asset,
uint256 _amount,
address _receiver
) public virtual {
_bridge(_rollupID, _asset, _amount, _receiver);
}

/**
* @notice Bridge a token to a L2 using Permit2.
* @dev Requires an off chain signature and the sender to have approved Permit2.
* @param _rollupID The Polygon Rollup ID to bridge to.
* @param _asset Token to bridge
* @param _amount The amount of `_asset` to bridge.
* @param _receiver The address to receive the tokens on the L2.
* @param _nonce The unique nonce for Permit2 to use.
* @param _deadline Timestamp the signature is good till.
* @param _signature Off chain Permit2 signature signed by msg.sender.
*/
function bridgePermit2(
uint32 _rollupID,
address _asset,
uint256 _amount,
address _receiver,
uint256 _nonce,
uint256 _deadline,
bytes calldata _signature
) public virtual {
// Transfer from using Permit2
PERMIT2.permitTransferFrom(
IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: _asset,
amount: _amount
}),
nonce: _nonce,
deadline: _deadline
}),
IPermit2.SignatureTransferDetails({
to: address(this),
requestedAmount: _amount
}),
msg.sender,
_signature
);

_bridgeToken(_rollupID, _asset, _amount, _receiver);
}

/**
* @notice Bridge WETH to a L2 using native ETH.
* @dev The amount used will be the msg.value.
* @param _rollupID The Polygon Rollup ID to bridge to.
* @param _receiver The address to receive the tokens on the L2.
*/
function bridgeEth(
uint32 _rollupID,
address _receiver
) public payable virtual {
wrapWETH9();
uint256 _amount = WETH9.balanceOf(address(this));
_bridgeToken(_rollupID, address(WETH9), _amount, _receiver);
}

/**
* @dev Pulls the token from the caller and bridges.
* Requires that approval to this contract has been given.
* AND that approval has been granted for the escrow to pull
* `_asset` from this contract.
*/
function _bridge(
uint32 _rollupID,
address _asset,
uint256 _amount,
address _receiver
) internal virtual {
pullToken(ERC20(_asset), _amount, address(this));
_bridgeToken(_rollupID, _asset, _amount, _receiver);
}

/**
* @dev Retrieves the escrow from the L1 Deployer and bridges to the L2.
* Will revert if no escrow has been deployed yet.
*/
function _bridgeToken(
uint32 _rollupID,
address _asset,
uint256 _amount,
address _receiver
) internal virtual {
L1YearnEscrow escrow = L1YearnEscrow(
L1DEPLOYER.getEscrow(_rollupID, _asset)
);

escrow.bridgeToken(_receiver, _amount, true);
}
}
Loading
Loading