131 lines
4.0 KiB
Solidity
131 lines
4.0 KiB
Solidity
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.19;
|
||
|
|
|
||
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||
|
|
|
||
|
|
interface IMintable {
|
||
|
|
function mint(address to, uint256 amount) external;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ICWMultiTokenBridgeL1 {
|
||
|
|
function lockAndSend(
|
||
|
|
address canonicalToken,
|
||
|
|
uint64 destinationChainSelector,
|
||
|
|
address recipient,
|
||
|
|
uint256 amount
|
||
|
|
) external payable returns (bytes32);
|
||
|
|
|
||
|
|
function calculateFee(
|
||
|
|
address canonicalToken,
|
||
|
|
uint64 destinationChainSelector,
|
||
|
|
address recipient,
|
||
|
|
uint256 amount
|
||
|
|
) external view returns (uint256);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Batch mint + bridge for c* mirror mesh prep on Chain 138.
|
||
|
|
* @dev Called by the operator EOA. Grant IMintable.MINTER_ROLE to this contract on each c* token.
|
||
|
|
* Multicall3 cannot replace this: subcalls would see msg.sender = Multicall3, not the operator.
|
||
|
|
*/
|
||
|
|
contract CWMirrorMeshBatch {
|
||
|
|
using SafeERC20 for IERC20;
|
||
|
|
|
||
|
|
address public immutable bridge;
|
||
|
|
address public immutable link;
|
||
|
|
|
||
|
|
event MintMany(address indexed operator, uint256 callCount);
|
||
|
|
event BridgeMany(address indexed operator, address indexed token, uint256 chunkCount, uint256 totalAmount);
|
||
|
|
|
||
|
|
constructor(address bridge_, address link_) {
|
||
|
|
require(bridge_ != address(0), "zero bridge");
|
||
|
|
require(link_ != address(0), "zero link");
|
||
|
|
bridge = bridge_;
|
||
|
|
link = link_;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// @dev Mint each amount to msg.sender (operator) in one transaction.
|
||
|
|
function mintMany(address[] calldata tokens, uint256[] calldata amounts) external {
|
||
|
|
uint256 n = tokens.length;
|
||
|
|
require(n == amounts.length, "length mismatch");
|
||
|
|
for (uint256 i; i < n; ) {
|
||
|
|
IMintable(tokens[i]).mint(msg.sender, amounts[i]);
|
||
|
|
unchecked {
|
||
|
|
++i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
emit MintMany(msg.sender, n);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// @dev Mint one token repeatedly to msg.sender (same token, many chunks) in one transaction.
|
||
|
|
function mintChunks(address token, uint256[] calldata amounts) external {
|
||
|
|
uint256 n = amounts.length;
|
||
|
|
for (uint256 i; i < n; ) {
|
||
|
|
IMintable(token).mint(msg.sender, amounts[i]);
|
||
|
|
unchecked {
|
||
|
|
++i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
emit MintMany(msg.sender, n);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @dev Bridge one canonical token in multiple lockAndSend chunks from msg.sender.
|
||
|
|
* Pulls total token + total LINK fee upfront, then loops lockAndSend.
|
||
|
|
*/
|
||
|
|
function bridgeTokenChunks(
|
||
|
|
address canonicalToken,
|
||
|
|
uint64 destinationChainSelector,
|
||
|
|
address recipient,
|
||
|
|
uint256[] calldata amounts
|
||
|
|
) external payable {
|
||
|
|
uint256 n = amounts.length;
|
||
|
|
require(n > 0, "empty amounts");
|
||
|
|
|
||
|
|
uint256 totalAmount;
|
||
|
|
uint256 totalFee;
|
||
|
|
for (uint256 i; i < n; ) {
|
||
|
|
totalAmount += amounts[i];
|
||
|
|
totalFee += ICWMultiTokenBridgeL1(bridge).calculateFee(
|
||
|
|
canonicalToken,
|
||
|
|
destinationChainSelector,
|
||
|
|
recipient,
|
||
|
|
amounts[i]
|
||
|
|
);
|
||
|
|
unchecked {
|
||
|
|
++i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
IERC20(canonicalToken).safeTransferFrom(msg.sender, address(this), totalAmount);
|
||
|
|
IERC20(link).safeTransferFrom(msg.sender, address(this), totalFee);
|
||
|
|
|
||
|
|
for (uint256 i; i < n; ) {
|
||
|
|
uint256 amount = amounts[i];
|
||
|
|
uint256 fee = ICWMultiTokenBridgeL1(bridge).calculateFee(
|
||
|
|
canonicalToken,
|
||
|
|
destinationChainSelector,
|
||
|
|
recipient,
|
||
|
|
amount
|
||
|
|
);
|
||
|
|
|
||
|
|
IERC20(canonicalToken).forceApprove(bridge, amount);
|
||
|
|
IERC20(link).forceApprove(bridge, fee);
|
||
|
|
|
||
|
|
ICWMultiTokenBridgeL1(bridge).lockAndSend(
|
||
|
|
canonicalToken,
|
||
|
|
destinationChainSelector,
|
||
|
|
recipient,
|
||
|
|
amount
|
||
|
|
);
|
||
|
|
|
||
|
|
unchecked {
|
||
|
|
++i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
emit BridgeMany(msg.sender, canonicalToken, n, totalAmount);
|
||
|
|
}
|
||
|
|
}
|