编译器
0.8.17+commit.8df45f5f
文件 1 的 6:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 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 functionCallWithValue(target, data, 0, "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");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, 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) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, 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) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
文件 2 的 6:CampaignManager.sol
pragma solidity 0.8.17;
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "openzeppelin-contracts/interfaces/IERC20.sol";
import {MerkleProof} from "openzeppelin-contracts/utils/cryptography/MerkleProof.sol";
contract CampaignManager {
using SafeERC20 for IERC20;
address private _oracle;
Campaign[] private _campaigns;
mapping(address => mapping(address => mapping(address => uint256)))
private _userClaims;
mapping(address => mapping(address => bytes32)) private _merkleRoots;
struct Campaign {
address token;
uint256 startTimestamp;
uint256 endTimestamp;
address rewardToken;
uint256 rewardAmount;
Options options;
address owner;
uint256 stoppedTimestamp;
}
struct Options {
uint8 indexMode;
bytes data;
}
event CampaignCreated(
uint256 id,
address token,
uint256 start,
uint256 end,
address rewardToken,
uint256 rewardAmount,
Options options,
address owner
);
event CampaignStopped(uint256 id, uint256 stoppedTimestamp);
event MerkleRootsUpdated(
address[] tokens,
address[] rewardTokens,
bytes32[] roots
);
event Claimed(
address account,
address token,
address rewardToken,
uint256 amount
);
event OracleUpdated(address oldOracle, address newOracle);
error CampaignDoesNotExist();
error CampaignNotRunning();
error ParamLengthMismatch();
error NotOwner();
error InvalidProof();
error InvalidAmount();
error InvalidOptions();
error NotOracle();
error InvalidOracle();
constructor() {
_oracle = msg.sender;
}
function create(
address token,
uint256 start,
uint256 end,
address rewardToken,
uint256 rewardAmount,
Options calldata options
) external returns (uint256) {
if (msg.sender != _oracle) {
if (rewardAmount == 0) {
revert InvalidAmount();
} else if (options.indexMode != 0) {
revert InvalidOptions();
}
}
_campaigns.push(
Campaign(
token,
start,
end,
rewardToken,
rewardAmount,
options,
msg.sender,
0
)
);
if (rewardAmount > 0) {
IERC20(rewardToken).safeTransferFrom(
msg.sender,
address(this),
rewardAmount
);
}
uint256 id = _campaigns.length - 1;
emit CampaignCreated(
id,
token,
start,
end,
rewardToken,
rewardAmount,
options,
msg.sender
);
return id;
}
function stop(uint256 id) public {
if (id >= campaignsCount()) {
revert CampaignDoesNotExist();
}
Campaign storage campaign = _campaigns[id];
if (campaign.owner != msg.sender) {
revert NotOwner();
}
uint256 stoppedTimestamp = block.timestamp;
if (
campaign.stoppedTimestamp > 0 ||
campaign.endTimestamp <= stoppedTimestamp
) {
revert CampaignNotRunning();
}
campaign.stoppedTimestamp = stoppedTimestamp;
if (campaign.rewardAmount > 0) {
uint256 refundAmount = ((campaign.endTimestamp - stoppedTimestamp) *
campaign.rewardAmount) /
(campaign.endTimestamp - campaign.startTimestamp);
IERC20(campaign.rewardToken).safeTransfer(msg.sender, refundAmount);
}
emit CampaignStopped(id, stoppedTimestamp);
}
function updateMerkleRoots(
address[] calldata tokens,
address[] calldata rewardTokens,
bytes32[] calldata merkleRoots
) public {
if (_oracle != msg.sender) {
revert NotOracle();
}
if (
tokens.length != merkleRoots.length ||
rewardTokens.length != merkleRoots.length
) {
revert ParamLengthMismatch();
}
for (uint256 i; i < tokens.length; ) {
_merkleRoots[tokens[i]][rewardTokens[i]] = merkleRoots[i];
unchecked {
++i;
}
}
emit MerkleRootsUpdated(tokens, rewardTokens, merkleRoots);
}
function claim(
address token,
address rewardToken,
uint256 earnedAmount,
uint256 claimAmount,
bytes32[] calldata merkleProof
) external {
bytes32 node = keccak256(abi.encodePacked(msg.sender, earnedAmount));
bytes32 root = merkleRoot(token, rewardToken);
if (!MerkleProof.verify(merkleProof, root, node)) {
revert InvalidProof();
}
uint256 newClaimedAmount = _userClaims[msg.sender][token][rewardToken] +
claimAmount;
if (newClaimedAmount > earnedAmount) {
revert InvalidAmount();
}
_userClaims[msg.sender][token][rewardToken] = newClaimedAmount;
IERC20(rewardToken).transfer(msg.sender, claimAmount);
emit Claimed(msg.sender, token, rewardToken, claimAmount);
}
function get(uint256 id) external view returns (Campaign memory) {
return _campaigns[id];
}
function campaignsCount() public view returns (uint256) {
return _campaigns.length;
}
function oracle() external view returns (address) {
return _oracle;
}
function userClaims(
address user,
address token,
address rewardToken
) external view returns (uint256) {
return _userClaims[user][token][rewardToken];
}
function merkleRoot(
address token,
address rewardToken
) public view returns (bytes32) {
return _merkleRoots[token][rewardToken];
}
function transferOracle(address newOracle) external {
if (msg.sender != _oracle) {
revert NotOracle();
}
if (newOracle == address(0) || newOracle == _oracle) {
revert InvalidOracle();
}
address oldOracle = _oracle;
_oracle = newOracle;
emit OracleUpdated(oldOracle, newOracle);
}
}
文件 3 的 6:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
文件 4 的 6:MerkleProof.sol
pragma solidity ^0.8.0;
library MerkleProof {
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
文件 5 的 6:SafeERC20.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 6 的 6:draft-IERC20Permit.sol
pragma solidity ^0.8.0;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
{
"compilationTarget": {
"src/CampaignManager.sol": "CampaignManager"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
":openzeppelin-erc20-basic/=lib/openzeppelin-contracts/contracts/token/ERC20/",
":openzeppelin-erc20-extensions/=lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/",
":openzeppelin-erc20/=lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/",
":openzeppelin-math/=lib/openzeppelin-contracts-upgradeable/contracts/utils/math/",
":openzeppelin-proxy/=lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/",
":openzeppelin-utils/=lib/openzeppelin-contracts-upgradeable/contracts/utils/"
]
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CampaignDoesNotExist","type":"error"},{"inputs":[],"name":"CampaignNotRunning","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidOptions","type":"error"},{"inputs":[],"name":"InvalidOracle","type":"error"},{"inputs":[],"name":"InvalidProof","type":"error"},{"inputs":[],"name":"NotOracle","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"ParamLengthMismatch","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"start","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"end","type":"uint256"},{"indexed":false,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"rewardAmount","type":"uint256"},{"components":[{"internalType":"uint8","name":"indexMode","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"indexed":false,"internalType":"struct CampaignManager.Options","name":"options","type":"tuple"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"CampaignCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stoppedTimestamp","type":"uint256"}],"name":"CampaignStopped","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"tokens","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"rewardTokens","type":"address[]"},{"indexed":false,"internalType":"bytes32[]","name":"roots","type":"bytes32[]"}],"name":"MerkleRootsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOracle","type":"address"},{"indexed":false,"internalType":"address","name":"newOracle","type":"address"}],"name":"OracleUpdated","type":"event"},{"inputs":[],"name":"campaignsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint256","name":"earnedAmount","type":"uint256"},{"internalType":"uint256","name":"claimAmount","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint256","name":"rewardAmount","type":"uint256"},{"components":[{"internalType":"uint8","name":"indexMode","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct CampaignManager.Options","name":"options","type":"tuple"}],"name":"create","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"get","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"startTimestamp","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint256","name":"rewardAmount","type":"uint256"},{"components":[{"internalType":"uint8","name":"indexMode","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct CampaignManager.Options","name":"options","type":"tuple"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"stoppedTimestamp","type":"uint256"}],"internalType":"struct CampaignManager.Campaign","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"}],"name":"merkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"stop","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOracle","type":"address"}],"name":"transferOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address[]","name":"rewardTokens","type":"address[]"},{"internalType":"bytes32[]","name":"merkleRoots","type":"bytes32[]"}],"name":"updateMerkleRoots","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"}],"name":"userClaims","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]