文件 1 的 6:Address.sol
pragma solidity ^0.8.0;
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) private pure returns (bytes memory) {
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
文件 2 的 6:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 3 的 6:MintForwarder.sol
pragma solidity 0.8.6;
import {MintUtil} from "./MintUtil.sol";
import {RateLimit} from "./RateLimit.sol";
contract MintForwarder is RateLimit {
address public tokenContract;
bool internal initialized;
event Mint(address indexed minter, address indexed to, uint256 amount);
function initialize(address newOwner, address newTokenContract) external onlyOwner {
require(!initialized, "MintForwarder: contract is already initialized");
require(newOwner != address(0), "MintForwarder: owner is the zero address");
require(newTokenContract != address(0), "MintForwarder: tokenContract is the zero address");
transferOwnership(newOwner);
tokenContract = newTokenContract;
initialized = true;
}
function mint(address _to, uint256 _amount) external onlyCallers {
require(_to != address(0), "MintForwarder: cannot mint to the zero address");
require(_amount > 0, "MintForwarder: mint amount not greater than 0");
_replenishAllowance(msg.sender);
require(_amount <= allowances[msg.sender], "MintForwarder: mint amount exceeds caller allowance");
allowances[msg.sender] = allowances[msg.sender] - _amount;
MintUtil.safeMint(_to, _amount, tokenContract);
emit Mint(msg.sender, _to, _amount);
}
}
文件 4 的 6:MintUtil.sol
pragma solidity 0.8.6;
import {Address} from "@openzeppelin4.2.0/contracts/utils/Address.sol";
library MintUtil {
bytes4 private constant _MINT_SELECTOR = bytes4(keccak256("mint(address,uint256)"));
function safeMint(address to, uint256 value, address tokenContract) internal {
bytes memory data = abi.encodeWithSelector(_MINT_SELECTOR, to, value);
Address.functionCall(tokenContract, data, "MinterUtil: mint failed");
}
}
文件 5 的 6:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_setOwner(_msgSender());
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 6 的 6:RateLimit.sol
pragma solidity 0.8.6;
import {Ownable} from "@openzeppelin4.2.0/contracts/access/Ownable.sol";
contract RateLimit is Ownable {
mapping(address => bool) public callers;
mapping(address => uint256) public intervals;
mapping(address => uint256) public allowancesLastSet;
mapping(address => uint256) public maxAllowances;
mapping(address => uint256) public allowances;
event CallerConfigured(address indexed caller, uint256 amount, uint256 interval);
event CallerRemoved(address indexed caller);
event AllowanceReplenished(address indexed caller, uint256 allowance, uint256 amountReplenished);
modifier onlyCallers() {
require(callers[msg.sender], "RateLimit: caller is not whitelisted");
_;
}
function configureCaller(address caller, uint256 amount, uint256 interval) external onlyOwner {
require(caller != address(0), "RateLimit: caller is the zero address");
require(amount > 0, "RateLimit: amount is zero");
require(interval > 0, "RateLimit: interval is zero");
callers[caller] = true;
maxAllowances[caller] = allowances[caller] = amount;
allowancesLastSet[caller] = block.timestamp;
intervals[caller] = interval;
emit CallerConfigured(caller, amount, interval);
}
function removeCaller(address caller) external onlyOwner {
callers[caller] = false;
emit CallerRemoved(caller);
}
function allowanceStored(address caller) external view returns (uint256) {
return allowances[caller];
}
function isCaller(address account) external view returns (bool) {
return callers[account];
}
function estimatedAllowance(address caller) external view returns (uint256) {
return allowances[caller] + _getReplenishAmount(caller);
}
function allowanceCurrent(address caller) public returns (uint256) {
_replenishAllowance(caller);
return allowances[caller];
}
function _replenishAllowance(address caller) internal {
if (allowances[caller] == maxAllowances[caller]) {
return;
}
uint256 amountToReplenish = _getReplenishAmount(caller);
if (amountToReplenish == 0) {
return;
}
allowances[caller] = allowances[caller] + amountToReplenish;
allowancesLastSet[caller] = block.timestamp;
emit AllowanceReplenished(caller, allowances[caller], amountToReplenish);
}
function _getReplenishAmount(address caller) internal view returns (uint256) {
uint256 secondsSinceAllowanceSet = block.timestamp - allowancesLastSet[caller];
uint256 amountToReplenish = (secondsSinceAllowanceSet * maxAllowances[caller]) / intervals[caller];
uint256 allowanceAfterReplenish = allowances[caller] + amountToReplenish;
if (allowanceAfterReplenish > maxAllowances[caller]) {
amountToReplenish = maxAllowances[caller] - allowances[caller];
}
return amountToReplenish;
}
}
{
"compilationTarget": {
"src/wrapped-tokens/MintForwarder.sol": "MintForwarder"
},
"evmVersion": "berlin",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@circlefin/stablecoin-evm/=lib/stablecoin-evm/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":@openzeppelin4.2.0/=lib/openzeppelin-contracts@v4.2.0/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
":openzeppelin-contracts@v4.2.0/=lib/openzeppelin-contracts@v4.2.0/contracts/",
":stablecoin-evm/=lib/stablecoin-evm/"
]
}
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"allowance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountReplenished","type":"uint256"}],"name":"AllowanceReplenished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"interval","type":"uint256"}],"name":"CallerConfigured","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"}],"name":"CallerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"minter","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"allowanceCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"allowanceStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"allowances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"allowancesLastSet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"callers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"caller","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interval","type":"uint256"}],"name":"configureCaller","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"estimatedAllowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"},{"internalType":"address","name":"newTokenContract","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"intervals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isCaller","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxAllowances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"removeCaller","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tokenContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]