The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/solidity/optics-xapps/contracts/bridge/BridgeRouter.sol

286 lines
11 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {TokenRegistry} from "./TokenRegistry.sol";
import {Router} from "../Router.sol";
import {IBridgeToken} from "../../interfaces/bridge/IBridgeToken.sol";
import {BridgeMessage} from "./BridgeMessage.sol";
// ============ External Imports ============
import {Home} from "@celo-org/optics-sol/contracts/Home.sol";
test: migrate tests and deploy process to ts (#400) * refactor: migrate js lib to ts * refactor: rewrite extendEnvironment in ts * add: sample tests * add: extend hre * refactor: ts deploy and signer type * refactor: devDeploy for tests * document: isTestDeploy param, add common.test.ts * add: common tests * fix: common tests * clean: remove sample script * refactor: remove Signer type and use ethers.Signer * refactor: typescript directory * build: generate typechain * refactor: typescript code (#399) * refactor: optics-tests/optics-deploy * fix: install typechain * fix * fix: ts errors * add: merkle tests (#417) * add: merkle tests * clean: remove commented code * fix: merkle tests * test: add queue tests (#422) * Add queue tests * fix: add await to fix nonce bug * test: home ts (#401) * add: home tests * add: home tests * add: deploy home (broken) * refactor: add num confirmations to config * refactor: clean up around ts tests (#424) - add testChain.ts with utility functions for making test Chain and Deploy - refactor the Optics hardhat extension - improve typing of enums in Optics lib - remove most references to waffle (prefer ethers) - remove isTestDeploy from deployment args in favor of a test? on Deploys * refactor: clean test deploy, home tests passing * refactor: deployOptics * fix: white space * lint: fix white space * refactor: testCase vectors and imports * fix: rust test vector generation * fix: missing await Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> * test: add message test (#423) * test: add message test * fix: replace require with import * fix: import for queue.test.ts * enhance: add type check * fix: import conflicts * test: add upgrade tests (#443) * test: add upgrade tests * fix: missing arg * refactor: clean up code * remove: unused code * refactor: deploy proxy * fix: unsaved code * fix: ts conflicts * add: utils ts (#453) * draft * refactor: remove waffle from home tests * fix: types, remove waffle Co-authored-by: yoduyodu <wang7ong@gmail.com> * test: add XAppConnectionManager tests (#456) * test: add XAppConnectionManager tests * test: initial setup for XAppConnectionManager tests * test: most XAppConnectionManager tests passing * fix: failing test * test: SimpleMessage ts (#468) * test: add cross-chain test utils * test: simpleMessage, mostly passing * fix: prove and process test * test: add initial state test, clean up * test: recoverymanager ts (#473) * test: add recoveryManager initial tests * fix: set recoveryManager in ts deploy * fix: set governor * test: replica ts (#483) * test: add replica tests * debug: fix failing tests * WIP: test: governance router/upgrade ts (#470) * test: add governance router tests * debug: some tests * debug: Gov Router tests * refactor: upgradeUtils * fix: missing await * test: clean up ts tests (#484) * clean: imports * refactor: utils * refactor: remove logs during testing * fix: weird bug, ethers.getSigners messes up describe blocks * delete: solidity/optics-core/js * update: pre-commit script for ts * update: pre-commit and scripts * fix: test, update with main * clean: types * fix: bad recipient handle * add: todo for gov router test * add: add back verify deploy stuff in js for now * bug: fix the governance upgrade test (#490) * fix: gov router upgrade test * feature: use TS in both solidity packages * bug: install deps in typescript dir in tests * feature: enhanced github action install * chore: disable automerge, and move lint before test Co-authored-by: Tong Wang <wang7ong@gmail.com> Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> Co-authored-by: James Prestwich <james@prestwi.ch>
3 years ago
import {TypeCasts} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/Initializable.sol";
/**
* @title BridgeRouter
*/
contract BridgeRouter is Initializable, Router, TokenRegistry {
using TypedMemView for bytes;
using TypedMemView for bytes29;
using BridgeMessage for bytes29;
using SafeERC20 for IERC20;
/// @notice 5 bps (0.05%) hardcoded fee. Can be changed by contract upgrade
uint256 public constant PRE_FILL_FEE_NUMERATOR = 9995;
uint256 public constant PRE_FILL_FEE_DENOMINATOR = 10000;
/// @notice A mapping that stores the LP that pre-filled a token transfer
/// message
mapping(bytes32 => address) public liquidityProvider;
// ======== External: Handle =========
/**
* @notice Handles an incoming message
* @param _origin The origin domain
* @param _sender The sender address
* @param _message The message
*/
function handle(
uint32 _origin,
bytes32 _sender,
bytes memory _message
) external override onlyReplica onlyRemoteRouter(_origin, _sender) {
// parse tokenId and action from message
bytes29 _msg = _message.ref(0).mustBeMessage();
bytes29 _tokenId = _msg.tokenId();
bytes29 _action = _msg.action();
// handle message based on the intended action
if (_action.isTransfer()) {
_handleTransfer(_tokenId, _action);
} else if (_action.isDetails()) {
_handleDetails(_tokenId, _action);
} else {
require(false, "!valid action");
}
}
// ======== External: Send Token =========
/**
* @notice Send tokens to a recipient on a remote chain
* @param _token The token address
* @param _amnt The amount
* @param _destination The destination domain
* @param _recipient The recipient address
*/
function send(
address _token,
uint256 _amnt,
uint32 _destination,
bytes32 _recipient
) external {
// get remote BridgeRouter address; revert if not found
bytes32 _remote = _mustHaveRemote(_destination);
// remove tokens from circulation on this chain
IERC20 _bridgeToken = IERC20(_token);
if (_isLocalOrigin(_bridgeToken)) {
// if the token originates on this chain, hold the tokens in escrow
// in the Router
_bridgeToken.safeTransferFrom(msg.sender, address(this), _amnt);
} else {
// if the token originates on a remote chain, burn the
// representation tokens on this chain
_downcast(_bridgeToken).burn(msg.sender, _amnt);
}
// format Transfer Tokens action
bytes29 _action = BridgeMessage.formatTransfer(_recipient, _amnt);
// send message to remote chain via Optics
Home(xAppConnectionManager.home()).enqueue(
_destination,
_remote,
BridgeMessage.formatMessage(_formatTokenId(_token), _action)
);
}
// ======== External: Fast Liquidity =========
/**
* @notice Allows a liquidity provider to give an
* end user fast liquidity by pre-filling an
* incoming transfer message.
* Transfers tokens from the liquidity provider to the end recipient, minus the LP fee;
* Records the liquidity provider, who receives
* the full token amount when the transfer message is handled.
* @param _message The incoming transfer message to pre-fill
*/
function preFill(bytes calldata _message) external {
// parse tokenId and action from message
bytes29 _msg = _message.ref(0).mustBeMessage();
bytes29 _tokenId = _msg.tokenId().mustBeTokenId();
bytes29 _action = _msg.action().mustBeTransfer();
// calculate prefill ID
bytes32 _id = _preFillId(_tokenId, _action);
// require that transfer has not already been pre-filled
require(liquidityProvider[_id] == address(0), "!unfilled");
// record liquidity provider
liquidityProvider[_id] = msg.sender;
// transfer tokens from liquidity provider to token recipient
IERC20 _token = _mustHaveToken(_tokenId);
_token.safeTransferFrom(
msg.sender,
_action.evmRecipient(),
_applyPreFillFee(_action.amnt())
);
}
// ======== External: Update Token Details =========
/**
* @notice Update the token metadata on another chain
* @param _token The token address
* @param _destination The destination domain
*/
// TODO: people can call this for nonsense non-ERC-20 tokens
// name, symbol, decimals could be nonsense
// remote chains will deploy a token contract based on this message
function updateDetails(address _token, uint32 _destination) external {
require(_isLocalOrigin(_token), "!local origin");
// get remote BridgeRouter address; revert if not found
bytes32 _remote = _mustHaveRemote(_destination);
// format Update Details message
IBridgeToken _bridgeToken = IBridgeToken(_token);
test: migrate tests and deploy process to ts (#400) * refactor: migrate js lib to ts * refactor: rewrite extendEnvironment in ts * add: sample tests * add: extend hre * refactor: ts deploy and signer type * refactor: devDeploy for tests * document: isTestDeploy param, add common.test.ts * add: common tests * fix: common tests * clean: remove sample script * refactor: remove Signer type and use ethers.Signer * refactor: typescript directory * build: generate typechain * refactor: typescript code (#399) * refactor: optics-tests/optics-deploy * fix: install typechain * fix * fix: ts errors * add: merkle tests (#417) * add: merkle tests * clean: remove commented code * fix: merkle tests * test: add queue tests (#422) * Add queue tests * fix: add await to fix nonce bug * test: home ts (#401) * add: home tests * add: home tests * add: deploy home (broken) * refactor: add num confirmations to config * refactor: clean up around ts tests (#424) - add testChain.ts with utility functions for making test Chain and Deploy - refactor the Optics hardhat extension - improve typing of enums in Optics lib - remove most references to waffle (prefer ethers) - remove isTestDeploy from deployment args in favor of a test? on Deploys * refactor: clean test deploy, home tests passing * refactor: deployOptics * fix: white space * lint: fix white space * refactor: testCase vectors and imports * fix: rust test vector generation * fix: missing await Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> * test: add message test (#423) * test: add message test * fix: replace require with import * fix: import for queue.test.ts * enhance: add type check * fix: import conflicts * test: add upgrade tests (#443) * test: add upgrade tests * fix: missing arg * refactor: clean up code * remove: unused code * refactor: deploy proxy * fix: unsaved code * fix: ts conflicts * add: utils ts (#453) * draft * refactor: remove waffle from home tests * fix: types, remove waffle Co-authored-by: yoduyodu <wang7ong@gmail.com> * test: add XAppConnectionManager tests (#456) * test: add XAppConnectionManager tests * test: initial setup for XAppConnectionManager tests * test: most XAppConnectionManager tests passing * fix: failing test * test: SimpleMessage ts (#468) * test: add cross-chain test utils * test: simpleMessage, mostly passing * fix: prove and process test * test: add initial state test, clean up * test: recoverymanager ts (#473) * test: add recoveryManager initial tests * fix: set recoveryManager in ts deploy * fix: set governor * test: replica ts (#483) * test: add replica tests * debug: fix failing tests * WIP: test: governance router/upgrade ts (#470) * test: add governance router tests * debug: some tests * debug: Gov Router tests * refactor: upgradeUtils * fix: missing await * test: clean up ts tests (#484) * clean: imports * refactor: utils * refactor: remove logs during testing * fix: weird bug, ethers.getSigners messes up describe blocks * delete: solidity/optics-core/js * update: pre-commit script for ts * update: pre-commit and scripts * fix: test, update with main * clean: types * fix: bad recipient handle * add: todo for gov router test * add: add back verify deploy stuff in js for now * bug: fix the governance upgrade test (#490) * fix: gov router upgrade test * feature: use TS in both solidity packages * bug: install deps in typescript dir in tests * feature: enhanced github action install * chore: disable automerge, and move lint before test Co-authored-by: Tong Wang <wang7ong@gmail.com> Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> Co-authored-by: James Prestwich <james@prestwi.ch>
3 years ago
bytes29 _action = BridgeMessage.formatDetails(
TypeCasts.coerceBytes32(_bridgeToken.name()),
TypeCasts.coerceBytes32(_bridgeToken.symbol()),
_bridgeToken.decimals()
);
// send message to remote chain via Optics
Home(xAppConnectionManager.home()).enqueue(
_destination,
_remote,
BridgeMessage.formatMessage(_formatTokenId(_token), _action)
);
}
// ============ Internal: Send / UpdateDetails ============
/**
* @notice Given a tokenAddress, format the tokenId
* identifier for the message.
* @param _token The token address
* @param _tokenId The bytes-encoded tokenId
*/
function _formatTokenId(address _token)
internal
view
returns (bytes29 _tokenId)
{
TokenId memory _tokId = _tokenIdFor(_token);
_tokenId = BridgeMessage.formatTokenId(_tokId.domain, _tokId.id);
}
// ============ Internal: Handle ============
/**
* @notice Handles an incoming Transfer message.
*
* If the token is of local origin, the amount is sent from escrow.
* Otherwise, a representation token is minted.
*
* @param _tokenId The token ID
* @param _action The action
*/
function _handleTransfer(bytes29 _tokenId, bytes29 _action)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
typeAssert(_action, BridgeMessage.Types.Transfer)
{
// get the token contract for the given tokenId on this chain;
// (if the token is of remote origin and there is
// no existing representation token contract, the TokenRegistry will
// deploy a new one)
IERC20 _token = _ensureToken(_tokenId);
address _recipient = _action.evmRecipient();
// If an LP has prefilled this token transfer,
// send the tokens to the LP instead of the recipient
bytes32 _id = _preFillId(_tokenId, _action);
address _lp = liquidityProvider[_id];
if (_lp != address(0)) {
_recipient = _lp;
delete liquidityProvider[_id];
}
// send the tokens into circulation on this chain
if (_isLocalOrigin(_token)) {
// if the token is of local origin, the tokens have been held in
// escrow in this contract
// while they have been circulating on remote chains;
// transfer the tokens to the recipient
_token.safeTransfer(_recipient, _action.amnt());
} else {
// if the token is of remote origin, mint the tokens to the
// recipient on this chain
_downcast(_token).mint(_recipient, _action.amnt());
}
}
/**
* @notice Handles an incoming Details message.
* @param _tokenId The token ID
* @param _action The action
*/
function _handleDetails(bytes29 _tokenId, bytes29 _action)
internal
typeAssert(_tokenId, BridgeMessage.Types.TokenId)
typeAssert(_action, BridgeMessage.Types.Details)
{
// get the token contract deployed on this chain
// revert otherwise
IERC20 _token = _mustHaveToken(_tokenId);
// require that the token is of remote origin
// (otherwise, the BridgeRouter did not deploy the token contract,
// and therefore cannot update its metadata)
require(!_isLocalOrigin(_token), "!remote origin");
// update the token metadata
_downcast(_token).setDetails(
_action.name(),
_action.symbol(),
_action.decimals()
);
}
// ============ Internal: Fast Liquidity ============
/**
* @notice Calculate the token amount after
* taking a 5 bps (0.05%) liquidity provider fee
* @param _amnt The token amount before the fee is taken
* @return _amtAfterFee The token amount after the fee is taken
*/
function _applyPreFillFee(uint256 _amnt)
internal
pure
returns (uint256 _amtAfterFee)
{
_amtAfterFee =
(_amnt * PRE_FILL_FEE_NUMERATOR) /
PRE_FILL_FEE_DENOMINATOR;
}
/**
* @notice get the prefillId used to identify
* fast liquidity provision for incoming token send messages
* @dev used to identify a token/transfer pair in the prefill LP mapping.
* NOTE: This approach has a weakness: a user can receive >1 batch of tokens of
* the same size, but only 1 will be eligible for fast liquidity. The
* other may only be filled at regular speed. This is because the messages
* will have identical `tokenId` and `action` fields. This seems fine,
* tbqh. A delay of a few hours on a corner case is acceptable in v1.
* @param _tokenId The token ID
* @param _action The action
*/
function _preFillId(bytes29 _tokenId, bytes29 _action)
internal
view
returns (bytes32)
{
bytes29[] memory _views = new bytes29[](2);
_views[0] = _tokenId;
_views[1] = _action;
return TypedMemView.joinKeccak(_views);
}
}