rs-2690: Fast warp routes (#2714)
### Description This PR introduces a feature to warp routes such that LPs can fill transactions on local chains and get a fee for doing this. ### Drive-by changes No. ### Related issues #2690 ### Backward compatibility No ### Testing Added Unit tests. --------- Co-authored-by: Rohan Shrothrium <rohan.shrothrium@intellecteu.com> Co-authored-by: Nam Chu Hoai <nambrot@googlemail.com>pull/2758/head
parent
d65c4e7e0a
commit
024058116e
@ -0,0 +1,53 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {HypERC20} from "./HypERC20.sol"; |
||||
|
||||
import {TokenRouter} from "./libs/TokenRouter.sol"; |
||||
import {FastTokenRouter} from "./libs/FastTokenRouter.sol"; |
||||
import {Message} from "./libs/Message.sol"; |
||||
|
||||
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; |
||||
|
||||
/** |
||||
* @title Hyperlane ERC20 Token Router that extends ERC20 with remote transfer functionality. |
||||
* @author Abacus Works |
||||
* @dev Supply on each chain is not constant but the aggregate supply across all chains is. |
||||
*/ |
||||
contract FastHypERC20 is FastTokenRouter, HypERC20 { |
||||
constructor(uint8 __decimals) HypERC20(__decimals) {} |
||||
|
||||
/** |
||||
* @dev delegates transfer logic to `_transferTo`. |
||||
* @inheritdoc TokenRouter |
||||
*/ |
||||
function _handle( |
||||
uint32 _origin, |
||||
bytes32 _sender, |
||||
bytes calldata _message |
||||
) internal virtual override(FastTokenRouter, TokenRouter) { |
||||
FastTokenRouter._handle(_origin, _sender, _message); |
||||
} |
||||
|
||||
/** |
||||
* @dev Mints `_amount` of tokens to `_recipient`. |
||||
* @inheritdoc FastTokenRouter |
||||
*/ |
||||
function _fastTransferTo(address _recipient, uint256 _amount) |
||||
internal |
||||
override |
||||
{ |
||||
_mint(_recipient, _amount); |
||||
} |
||||
|
||||
/** |
||||
* @dev Burns `_amount` of tokens from `_recipient`. |
||||
* @inheritdoc FastTokenRouter |
||||
*/ |
||||
function _fastRecieveFrom(address _sender, uint256 _amount) |
||||
internal |
||||
override |
||||
{ |
||||
_burn(_sender, _amount); |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {HypERC20Collateral} from "./HypERC20Collateral.sol"; |
||||
import {TokenRouter} from "./libs/TokenRouter.sol"; |
||||
import {FastTokenRouter} from "./libs/FastTokenRouter.sol"; |
||||
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
||||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
||||
|
||||
/** |
||||
* @title Hyperlane ERC20 Token Collateral that wraps an existing ERC20 with remote transfer functionality. |
||||
* @author Abacus Works |
||||
*/ |
||||
contract FastHypERC20Collateral is FastTokenRouter, HypERC20Collateral { |
||||
using SafeERC20 for IERC20; |
||||
|
||||
/** |
||||
* @notice Constructor |
||||
* @param erc20 Address of the token to keep as collateral |
||||
*/ |
||||
constructor(address erc20) HypERC20Collateral(erc20) {} |
||||
|
||||
/** |
||||
* @dev delegates transfer logic to `_transferTo`. |
||||
* @inheritdoc FastTokenRouter |
||||
*/ |
||||
function _handle( |
||||
uint32 _origin, |
||||
bytes32 _sender, |
||||
bytes calldata _message |
||||
) internal virtual override(FastTokenRouter, TokenRouter) { |
||||
FastTokenRouter._handle(_origin, _sender, _message); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers `_amount` of `wrappedToken` to `_recipient`. |
||||
* @inheritdoc FastTokenRouter |
||||
*/ |
||||
function _fastTransferTo(address _recipient, uint256 _amount) |
||||
internal |
||||
override |
||||
{ |
||||
wrappedToken.safeTransfer(_recipient, _amount); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers in `_amount` of `wrappedToken` from `_recipient`. |
||||
* @inheritdoc FastTokenRouter |
||||
*/ |
||||
function _fastRecieveFrom(address _sender, uint256 _amount) |
||||
internal |
||||
override |
||||
{ |
||||
wrappedToken.safeTransferFrom(_sender, address(this), _amount); |
||||
} |
||||
} |
@ -0,0 +1,208 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {Message} from "./Message.sol"; |
||||
import {TokenRouter} from "./TokenRouter.sol"; |
||||
|
||||
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; |
||||
|
||||
/** |
||||
* @title Common FastTokenRouter functionality for ERC20 Tokens with remote transfer support. |
||||
* @author Abacus Works |
||||
*/ |
||||
abstract contract FastTokenRouter is TokenRouter { |
||||
using TypeCasts for bytes32; |
||||
using Message for bytes; |
||||
|
||||
uint256 public fastTransferId; |
||||
// maps `fastTransferId` to the filler address. |
||||
mapping(bytes32 => address) filledFastTransfers; |
||||
|
||||
/** |
||||
* @dev delegates transfer logic to `_transferTo`. |
||||
* @inheritdoc TokenRouter |
||||
*/ |
||||
function _handle( |
||||
uint32 _origin, |
||||
bytes32, |
||||
bytes calldata _message |
||||
) internal virtual override { |
||||
bytes32 recipient = _message.recipient(); |
||||
uint256 amount = _message.amount(); |
||||
bytes calldata metadata = _message.metadata(); |
||||
_transferTo(recipient.bytes32ToAddress(), amount, _origin, metadata); |
||||
emit ReceivedTransferRemote(_origin, recipient, amount); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers `_amount` of token to `_recipient`/`fastFiller` who provided LP. |
||||
* @dev Called by `handle` after message decoding. |
||||
*/ |
||||
function _transferTo( |
||||
address _recipient, |
||||
uint256 _amount, |
||||
uint32 _origin, |
||||
bytes calldata _metadata |
||||
) internal virtual { |
||||
address _tokenRecipient = _getTokenRecipient( |
||||
_recipient, |
||||
_amount, |
||||
_origin, |
||||
_metadata |
||||
); |
||||
|
||||
_fastTransferTo(_tokenRecipient, _amount); |
||||
} |
||||
|
||||
/** |
||||
* @dev allows an external user to full an unfilled fast transfer order. |
||||
* @param _recipient The recepient of the wrapped token on base chain. |
||||
* @param _amount The amount of wrapped tokens that is being bridged. |
||||
* @param _fastFee The fee the bridging entity will pay. |
||||
* @param _fastTransferId Id assigned on the remote chain to uniquely identify the transfer. |
||||
*/ |
||||
function fillFastTransfer( |
||||
address _recipient, |
||||
uint256 _amount, |
||||
uint256 _fastFee, |
||||
uint32 _origin, |
||||
uint256 _fastTransferId |
||||
) external virtual { |
||||
bytes32 filledFastTransfersKey = _getFastTransfersKey( |
||||
_origin, |
||||
_fastTransferId, |
||||
_amount, |
||||
_fastFee, |
||||
_recipient |
||||
); |
||||
require( |
||||
filledFastTransfers[filledFastTransfersKey] == address(0), |
||||
"request already filled" |
||||
); |
||||
|
||||
filledFastTransfers[filledFastTransfersKey] = msg.sender; |
||||
|
||||
_fastRecieveFrom(msg.sender, _amount - _fastFee); |
||||
_fastTransferTo(_recipient, _amount - _fastFee); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers `_amountOrId` token to `_recipient` on `_destination` domain. |
||||
* @dev Delegates transfer logic to `_fastTransferFromSender` implementation. |
||||
* @dev Emits `SentTransferRemote` event on the origin chain. |
||||
* @param _destination The identifier of the destination chain. |
||||
* @param _recipient The address of the recipient on the destination chain. |
||||
* @param _amountOrId The amount or identifier of tokens to be sent to the remote recipient. |
||||
* @return messageId The identifier of the dispatched message. |
||||
*/ |
||||
function fastTransferRemote( |
||||
uint32 _destination, |
||||
bytes32 _recipient, |
||||
uint256 _amountOrId, |
||||
uint256 _fastFee |
||||
) public payable virtual returns (bytes32 messageId) { |
||||
uint256 _fastTransferId = fastTransferId; |
||||
fastTransferId = _fastTransferId + 1; |
||||
bytes memory metadata = _fastTransferFromSender( |
||||
_amountOrId, |
||||
_fastFee, |
||||
_fastTransferId + 1 |
||||
); |
||||
|
||||
messageId = _dispatchWithGas( |
||||
_destination, |
||||
Message.format(_recipient, _amountOrId, metadata), |
||||
msg.value, // interchain gas payment |
||||
msg.sender // refund address |
||||
); |
||||
emit SentTransferRemote(_destination, _recipient, _amountOrId); |
||||
} |
||||
|
||||
/** |
||||
* @dev Burns `_amount` of token from `msg.sender` balance. |
||||
* @dev Pays `_fastFee` of tokens to LP on source chain. |
||||
* @dev Returns `fastFee` as bytes in the form of metadata. |
||||
*/ |
||||
function _fastTransferFromSender( |
||||
uint256 _amount, |
||||
uint256 _fastFee, |
||||
uint256 _fastTransferId |
||||
) internal virtual returns (bytes memory) { |
||||
_fastRecieveFrom(msg.sender, _amount); |
||||
return abi.encode(_fastFee, _fastTransferId); |
||||
} |
||||
|
||||
/** |
||||
* @dev returns an address that indicates who should recieve the bridged tokens. |
||||
* @dev if _fastFees was inlcuded and someone filled the order before the mailbox made the contract call, the filler gets the funds. |
||||
*/ |
||||
function _getTokenRecipient( |
||||
address _recipient, |
||||
uint256 _amount, |
||||
uint32 _origin, |
||||
bytes calldata _metadata |
||||
) internal view returns (address) { |
||||
if (_metadata.length == 0) { |
||||
return _recipient; |
||||
} |
||||
|
||||
// decode metadata to extract `_fastFee` and `_fastTransferId`. |
||||
(uint256 _fastFee, uint256 _fastTransferId) = abi.decode( |
||||
_metadata, |
||||
(uint256, uint256) |
||||
); |
||||
|
||||
address _fillerAddress = filledFastTransfers[ |
||||
_getFastTransfersKey( |
||||
_origin, |
||||
_fastTransferId, |
||||
_amount, |
||||
_fastFee, |
||||
_recipient |
||||
) |
||||
]; |
||||
if (_fillerAddress != address(0)) { |
||||
return _fillerAddress; |
||||
} |
||||
|
||||
return _recipient; |
||||
} |
||||
|
||||
/** |
||||
* @dev generates the key for storing the filler address of fast transfers. |
||||
*/ |
||||
function _getFastTransfersKey( |
||||
uint32 _origin, |
||||
uint256 _fastTransferId, |
||||
uint256 _amount, |
||||
uint256 _fastFee, |
||||
address _recipient |
||||
) internal pure returns (bytes32) { |
||||
return |
||||
keccak256( |
||||
abi.encodePacked( |
||||
_origin, |
||||
_fastTransferId, |
||||
_amount, |
||||
_fastFee, |
||||
_recipient |
||||
) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dev Should transfer `_amount` of tokens to `_recipient`. |
||||
* @dev The implementation is delegated. |
||||
*/ |
||||
function _fastTransferTo(address _recipient, uint256 _amount) |
||||
internal |
||||
virtual; |
||||
|
||||
/** |
||||
* @dev Should collect `amount` of tokens from `_sender`. |
||||
* @dev The implementation is delegated. |
||||
*/ |
||||
function _fastRecieveFrom(address _sender, uint256 _amount) |
||||
internal |
||||
virtual; |
||||
} |
Loading…
Reference in new issue