Files
smom-dbis-138/contracts/bridge/atomic/CWMultiTokenBridgeSettlementAdapter.sol
2026-07-08 10:02:12 -07:00

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");
}
}