108 lines
4.1 KiB
Solidity
108 lines
4.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import {IAtomicSettlementAdapter} from "./interfaces/IAtomicSettlementAdapter.sol";
|
|
|
|
interface ICWMultiTokenBridgeL1Settlement {
|
|
function calculateFee(address canonicalToken, uint64 destinationChainSelector, address recipient, uint256 amount)
|
|
external
|
|
view
|
|
returns (uint256);
|
|
|
|
function feeToken() external view returns (address);
|
|
|
|
function lockAndSend(address canonicalToken, uint64 destinationChainSelector, address recipient, uint256 amount)
|
|
external
|
|
payable
|
|
returns (bytes32 messageId);
|
|
}
|
|
|
|
/**
|
|
* @title CWMultiTokenBridgeSettlementAdapter
|
|
* @notice Replenishes a fulfiller/vault by forwarding coordinator settlement
|
|
* funds into CWMultiTokenBridgeL1.lockAndSend.
|
|
*/
|
|
contract CWMultiTokenBridgeSettlementAdapter is IAtomicSettlementAdapter {
|
|
using SafeERC20 for IERC20;
|
|
|
|
ICWMultiTokenBridgeL1Settlement public immutable bridge;
|
|
uint64 public immutable defaultDestinationSelector;
|
|
|
|
event CwSettlementSent(
|
|
bytes32 indexed obligationId,
|
|
bytes32 indexed messageId,
|
|
address indexed token,
|
|
address recipient,
|
|
uint64 destinationSelector,
|
|
uint256 amount,
|
|
uint256 fee
|
|
);
|
|
|
|
error ZeroAddress();
|
|
error FeeTooLow(uint256 requiredFee, uint256 providedFee);
|
|
error InvalidSettlementData();
|
|
|
|
constructor(address bridge_, uint64 defaultDestinationSelector_) {
|
|
if (bridge_ == address(0)) revert ZeroAddress();
|
|
bridge = ICWMultiTokenBridgeL1Settlement(bridge_);
|
|
defaultDestinationSelector = defaultDestinationSelector_;
|
|
}
|
|
|
|
function executeSettlement(
|
|
bytes32 obligationId,
|
|
address token,
|
|
uint256 amount,
|
|
address recipient,
|
|
bytes calldata data
|
|
) external payable returns (bytes32 settlementId) {
|
|
(address settlementRecipient, uint64 destinationSelector, uint256 maxFee) =
|
|
_decodeSettlementData(recipient, data);
|
|
uint256 fee = bridge.calculateFee(token, destinationSelector, settlementRecipient, amount);
|
|
if (maxFee != 0 && fee > maxFee) revert FeeTooLow(fee, maxFee);
|
|
|
|
IERC20(token).forceApprove(address(bridge), amount);
|
|
address feeToken = bridge.feeToken();
|
|
if (feeToken == address(0)) {
|
|
if (msg.value < fee) revert FeeTooLow(fee, msg.value);
|
|
settlementId = bridge.lockAndSend{value: fee}(token, destinationSelector, settlementRecipient, amount);
|
|
_refundNativeExcess(fee);
|
|
} else {
|
|
if (IERC20(feeToken).balanceOf(address(this)) < fee) {
|
|
revert FeeTooLow(fee, IERC20(feeToken).balanceOf(address(this)));
|
|
}
|
|
IERC20(feeToken).forceApprove(address(bridge), fee);
|
|
settlementId = bridge.lockAndSend(token, destinationSelector, settlementRecipient, amount);
|
|
}
|
|
|
|
emit CwSettlementSent(obligationId, settlementId, token, settlementRecipient, destinationSelector, amount, fee);
|
|
}
|
|
|
|
function _decodeSettlementData(address fallbackRecipient, bytes calldata data)
|
|
internal
|
|
view
|
|
returns (address settlementRecipient, uint64 destinationSelector, uint256 maxFee)
|
|
{
|
|
if (data.length == 0) {
|
|
settlementRecipient = fallbackRecipient;
|
|
destinationSelector = defaultDestinationSelector;
|
|
} else {
|
|
if (data.length != 96) revert InvalidSettlementData();
|
|
(settlementRecipient, destinationSelector, maxFee) = abi.decode(data, (address, uint64, uint256));
|
|
if (destinationSelector == 0) {
|
|
destinationSelector = defaultDestinationSelector;
|
|
}
|
|
}
|
|
if (settlementRecipient == address(0)) revert ZeroAddress();
|
|
}
|
|
|
|
function _refundNativeExcess(uint256 feeUsed) internal {
|
|
if (msg.value <= feeUsed) {
|
|
return;
|
|
}
|
|
(bool ok,) = payable(msg.sender).call{value: msg.value - feeUsed}("");
|
|
require(ok, "CWMultiTokenBridgeSettlementAdapter: refund failed");
|
|
}
|
|
}
|