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/contracts/token/libs/FastTokenRouter.sol

209 lines
6.4 KiB

// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {TokenMessage} from "./TokenMessage.sol";
import {TokenRouter} from "./TokenRouter.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 TokenMessage 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 + 1;
fastTransferId = _fastTransferId;
bytes memory metadata = _fastTransferFromSender(
_amountOrId,
_fastFee,
_fastTransferId
);
messageId = _dispatch(
_destination,
TokenMessage.format(_recipient, _amountOrId, metadata)
);
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;
}