编译器
0.8.25+commit.b61c2a91
文件 1 的 20:Address.sol
pragma solidity ^0.8.20;
library Address {
error AddressInsufficientBalance(address account);
error AddressEmptyCode(address target);
error FailedInnerCall();
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
function _revert(bytes memory returndata) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
文件 2 的 20:AggregatorV3Interface.sol
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(
uint80 _roundId
) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
文件 3 的 20:Common.sol
pragma solidity 0.8.25;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
IERC20 constant ETH = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
error ZeroAddress();
error ZeroLengthArray();
error IdenticalValue();
error ArrayLengthMismatch();
error InvalidSignature();
error InvalidData();
文件 4 的 20:Context.sol
pragma solidity ^0.8.20;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
文件 5 的 20:ECDSA.sol
pragma solidity ^0.8.20;
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
error ECDSAInvalidSignature();
error ECDSAInvalidSignatureLength(uint256 length);
error ECDSAInvalidSignatureS(bytes32 s);
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError, bytes32) {
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return;
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
文件 6 的 20:IERC20.sol
pragma solidity ^0.8.20;
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 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
文件 7 的 20:IERC20Permit.sol
pragma solidity ^0.8.20;
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);
}
文件 8 的 20:ILockup.sol
pragma solidity ^0.8.0;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IRounds } from "./IRounds.sol";
interface ILockup {
function stakes(address user, uint256 index) external view returns (uint256 amount, uint256 endTime);
}
interface IPreSale is IRounds {
function purchaseWithClaim(
IERC20 token,
uint256 tokenPrice,
uint8 referenceNormalizationFactor,
uint256 amount,
uint256 minAmountToken,
uint256[] calldata indexes,
address recipient,
uint32 round
) external payable;
}
文件 9 的 20:IRounds.sol
pragma solidity 0.8.25;
interface IRounds {
function rounds(uint32 round) external view returns (uint256 startTime, uint256 endTime, uint256 price);
}
文件 10 的 20:Math.sol
pragma solidity ^0.8.20;
library Math {
error MathOverflowedMulDiv();
enum Rounding {
Floor,
Ceil,
Trunc,
Expand
}
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a & b) + (a ^ b) / 2;
}
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
return a / b;
}
return a == 0 ? 0 : (a - 1) / b + 1;
}
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
uint256 prod0 = x * y;
uint256 prod1;
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
return prod0 / denominator;
}
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
uint256 remainder;
assembly {
remainder := mulmod(x, y, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
uint256 twos = denominator & (0 - denominator);
assembly {
denominator := div(denominator, twos)
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
uint256 inverse = (3 * denominator) ^ 2;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
result = prod0 * inverse;
return result;
}
}
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 result = 1 << (log2(a) >> 1);
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
文件 11 的 20:MessageHashUtils.sol
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
library MessageHashUtils {
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32")
mstore(0x1c, messageHash)
digest := keccak256(0x00, 0x3c)
}
}
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
assembly {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}
文件 12 的 20:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 13 的 20:Ownable2Step.sol
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}
文件 14 的 20:PreSale.sol
pragma solidity 0.8.25;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { Rounds, Ownable } from "./Rounds.sol";
import { ILockup, IPreSale } from "./ILockup.sol";
import "./Common.sol";
contract PreSale is IPreSale, Rounds, ReentrancyGuard {
using SafeERC20 for IERC20;
using Address for address payable;
struct ClaimNFT {
uint256[] nftAmounts;
uint256 roundPrice;
}
struct TokenInfo {
uint256 latestPrice;
uint8 normalizationFactorForToken;
uint8 normalizationFactorForNFT;
}
bool public buyEnable = true;
address public signerWallet;
address public claimsContract;
address[] public fundsWallets;
uint256 public totalPurchases;
uint256 private constant NORMALIZARION_FACTOR = 1e30;
uint256 private constant MIN_LOCKED_AMOUNT = 5555e18;
uint256 private constant PPH = 100;
uint256 private constant FIRST_ROUND = 20;
uint256 private constant OTHER_ROUND = 7;
uint256[] public nftPricing;
ILockup public immutable lockup;
mapping(address => mapping(uint32 => uint256)) public claims;
mapping(address => bool) public blacklistAddress;
mapping(address => mapping(uint32 => ClaimNFT[])) public claimNFT;
event PurchasedWithETH(
address indexed by,
string code,
uint256 amountPurchasedETH,
uint32 indexed round,
uint256 indexed roundPrice,
uint256 tokenPurchased
);
event PurchasedWithToken(
IERC20 indexed token,
uint256 tokenPrice,
address indexed by,
string code,
uint256 amountPurchased,
uint256 tokenPurchased,
uint32 indexed round
);
event PurchasedWithETHForNFT(
address indexed by,
string code,
uint256 amountInETH,
uint256 ethPrice,
uint32 indexed round,
uint256 roundPrice,
uint256[] nftAmounts
);
event PurchasedWithTokenForNFT(
IERC20 indexed token,
uint256 tokenPrice,
address indexed by,
string code,
uint256 amountPurchased,
uint32 indexed round,
uint256 roundPrice,
uint256[] nftAmounts
);
event PurchasedWithClaimAmount(
address indexed by,
uint256 amount,
IERC20 token,
uint32 indexed round,
uint256 indexed tokenPrice,
uint256 tokenPurchased
);
event SignerUpdated(address oldSigner, address newSigner);
event FundsWalletsUpdated(address[] indexed oldAddresses, address[] indexed newAddresses);
event BlacklistUpdated(address which, bool accessNow);
event BuyEnableUpdated(bool oldAccess, bool newAccess);
event PricingUpdated(uint256 oldPrice, uint256 newPrice);
error Blacklisted();
error BuyNotEnable();
error DeadlineExpired();
error UnexpectedPriceDifference();
error ZeroValue();
error PriceNotFound();
error OnlyClaims();
error InvalidPurchase();
error CodeSyncIssue();
error PriceNotUpdated();
error RoundIdNotUpdated();
error InvalidValue();
error ArrayNotSorted();
modifier checkAddressZero(address which) {
if (which == address(0)) {
revert ZeroAddress();
}
_;
}
modifier canBuy() {
if (!buyEnable) {
revert BuyNotEnable();
}
_;
}
constructor(
address[] memory fundsWalletAddresses,
address signerAddress,
address claimsContractAddress,
ILockup lockupContractAddress,
address owner,
uint32 lastRound,
uint256[] memory nftPrices
) Rounds(lastRound) Ownable(owner) {
if (
signerAddress == address(0) ||
claimsContractAddress == address(0) ||
address(lockupContractAddress) == address(0)
) {
revert ZeroAddress();
}
_verifyFundsWallets(fundsWalletAddresses);
fundsWallets = fundsWalletAddresses;
signerWallet = signerAddress;
claimsContract = claimsContractAddress;
lockup = lockupContractAddress;
if (nftPrices.length == 0) {
revert ZeroLengthArray();
}
for (uint256 i = 0; i < nftPrices.length; ++i) {
_checkValue(nftPrices[i]);
}
nftPricing = nftPrices;
}
function enableBuy(bool enabled) external onlyOwner {
if (buyEnable == enabled) {
revert IdenticalValue();
}
emit BuyEnableUpdated({ oldAccess: buyEnable, newAccess: enabled });
buyEnable = enabled;
}
function changeSigner(address newSigner) external checkAddressZero(newSigner) onlyOwner {
address oldSigner = signerWallet;
if (oldSigner == newSigner) {
revert IdenticalValue();
}
emit SignerUpdated({ oldSigner: oldSigner, newSigner: newSigner });
signerWallet = newSigner;
}
function changeFundsWallets(address[] calldata newFundsWallets) external onlyOwner {
_verifyFundsWallets(newFundsWallets);
emit FundsWalletsUpdated({ oldAddresses: fundsWallets, newAddresses: newFundsWallets });
fundsWallets = newFundsWallets;
}
function updateBlackListedUser(address which, bool access) external checkAddressZero(which) onlyOwner {
bool oldAccess = blacklistAddress[which];
if (oldAccess == access) {
revert IdenticalValue();
}
emit BlacklistUpdated({ which: which, accessNow: access });
blacklistAddress[which] = access;
}
function updatePricing(uint256[] calldata newPrices) external onlyOwner {
uint256[] memory oldPrices = nftPricing;
for (uint256 i = 0; i < newPrices.length; ++i) {
uint256 newPrice = newPrices[i];
_checkValue(newPrice);
emit PricingUpdated({ oldPrice: oldPrices[i], newPrice: newPrice });
}
nftPricing = newPrices;
}
function getNFTClaims(address buyer, uint32 round) external view returns (ClaimNFT[] memory) {
ClaimNFT[] memory nftClaim = claimNFT[buyer][round];
return nftClaim;
}
function purchaseTokenWithETH(
string memory code,
uint32 round,
uint256 deadline,
uint256 minAmountToken,
uint256[] calldata indexes,
uint8 v,
bytes32 r,
bytes32 s
) external payable nonReentrant canBuy {
_validatePurchaseWithETH(msg.value, round, deadline, code, v, r, s);
uint256 roundPrice = _getRoundPriceForToken(msg.sender, indexes, round, ETH);
TokenInfo memory tokenInfo = getLatestPrice(ETH);
if (tokenInfo.latestPrice == 0) {
revert PriceNotFound();
}
uint256 toReturn = _calculateAndUpdateTokenAmount(
msg.value,
tokenInfo.latestPrice,
tokenInfo.normalizationFactorForToken,
roundPrice
);
if (toReturn < minAmountToken) {
revert UnexpectedPriceDifference();
}
claims[msg.sender][round] += toReturn;
(uint256 amountEach, uint256 equivalence) = _calculateTransferAmount(msg.value);
payable(claimsContract).sendValue(equivalence);
_sendETH(amountEach);
emit PurchasedWithETH({
by: msg.sender,
code: code,
amountPurchasedETH: msg.value,
round: round,
roundPrice: roundPrice,
tokenPurchased: toReturn
});
}
function purchaseTokenWithToken(
IERC20 token,
uint8 referenceNormalizationFactor,
uint256 referenceTokenPrice,
uint256 purchaseAmount,
uint256 minAmountToken,
uint256[] calldata indexes,
string memory code,
uint32 round,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external canBuy nonReentrant {
_validatePurchaseWithToken(
token,
round,
deadline,
code,
referenceTokenPrice,
referenceNormalizationFactor,
v,
r,
s
);
_checkValue(purchaseAmount);
uint256 roundPrice = _getRoundPriceForToken(msg.sender, indexes, round, token);
(uint256 latestPrice, uint8 normalizationFactor) = _validatePrice(
token,
referenceTokenPrice,
referenceNormalizationFactor
);
uint256 toReturn = _calculateAndUpdateTokenAmount(purchaseAmount, latestPrice, normalizationFactor, roundPrice);
if (toReturn < minAmountToken) {
revert UnexpectedPriceDifference();
}
claims[msg.sender][round] += toReturn;
(uint256 amountEach, uint256 equivalence) = _calculateTransferAmount(purchaseAmount);
token.safeTransferFrom(msg.sender, claimsContract, equivalence);
_sendToken(token, amountEach);
emit PurchasedWithToken({
token: token,
tokenPrice: latestPrice,
by: msg.sender,
code: code,
amountPurchased: purchaseAmount,
tokenPurchased: toReturn,
round: round
});
}
function purchaseNFTWithETH(
string memory code,
uint32 round,
uint256[] calldata nftAmounts,
uint256 deadline,
uint256[] calldata indexes,
uint8 v,
bytes32 r,
bytes32 s
) external payable canBuy nonReentrant {
uint256[] memory nftPrices = nftPricing;
_validateArrays(nftAmounts.length, nftPrices.length);
_validatePurchaseWithETH(msg.value, round, deadline, code, v, r, s);
TokenInfo memory tokenInfo = getLatestPrice(ETH);
if (tokenInfo.latestPrice == 0) {
revert PriceNotFound();
}
(uint256 value, uint256 roundPrice) = _processPurchaseNFT(
ETH,
tokenInfo.latestPrice,
tokenInfo.normalizationFactorForNFT,
round,
indexes,
nftAmounts,
nftPrices
);
if (msg.value < value) {
revert InvalidPurchase();
}
_checkValue(value);
uint256 amountUnused = msg.value - value;
if (amountUnused > 0) {
payable(msg.sender).sendValue(amountUnused);
}
(uint256 amountEach, uint256 equivalence) = _calculateTransferAmount(value);
payable(claimsContract).sendValue(equivalence);
_sendETH(amountEach);
emit PurchasedWithETHForNFT({
by: msg.sender,
code: code,
amountInETH: value,
ethPrice: tokenInfo.latestPrice,
round: round,
roundPrice: roundPrice,
nftAmounts: nftAmounts
});
}
function purchaseNFTWithToken(
IERC20 token,
uint256 referenceTokenPrice,
uint8 referenceNormalizationFactor,
string memory code,
uint32 round,
uint256[] calldata nftAmounts,
uint256 deadline,
uint256[] calldata indexes,
uint8 v,
bytes32 r,
bytes32 s
) external canBuy nonReentrant {
uint256[] memory nftPrices = nftPricing;
_validateArrays(nftAmounts.length, nftPrices.length);
_validatePurchaseWithToken(
token,
round,
deadline,
code,
referenceTokenPrice,
referenceNormalizationFactor,
v,
r,
s
);
TokenInfo memory tokenInfo = getLatestPrice(token);
if (tokenInfo.latestPrice != 0) {
if (referenceTokenPrice != 0 || referenceNormalizationFactor != 0) {
revert CodeSyncIssue();
}
}
if (tokenInfo.latestPrice == 0) {
if (referenceTokenPrice == 0 || referenceNormalizationFactor == 0) {
revert ZeroValue();
}
tokenInfo.latestPrice = referenceTokenPrice;
tokenInfo.normalizationFactorForNFT = referenceNormalizationFactor;
}
(uint256 value, uint256 roundPrice) = _processPurchaseNFT(
token,
tokenInfo.latestPrice,
tokenInfo.normalizationFactorForNFT,
round,
indexes,
nftAmounts,
nftPrices
);
_checkValue(value);
(uint256 amountEach, uint256 equivalence) = _calculateTransferAmount(value);
token.safeTransferFrom(msg.sender, claimsContract, equivalence);
_sendToken(token, amountEach);
emit PurchasedWithTokenForNFT({
token: token,
tokenPrice: tokenInfo.latestPrice,
by: msg.sender,
code: code,
amountPurchased: value,
round: round,
roundPrice: roundPrice,
nftAmounts: nftAmounts
});
}
function purchaseWithClaim(
IERC20 token,
uint256 referenceTokenPrice,
uint8 referenceNormalizationFactor,
uint256 amount,
uint256 minAmountToken,
uint256[] calldata indexes,
address recipient,
uint32 round
) external payable canBuy nonReentrant {
if (msg.sender != claimsContract) {
revert OnlyClaims();
}
_checkBlacklist(recipient);
if (!allowedTokens[round][token].access) {
revert TokenDisallowed();
}
uint256 roundPrice = _getRoundPriceForToken(recipient, indexes, round, token);
(uint256 latestPrice, uint8 normalizationFactor) = _validatePrice(
token,
referenceTokenPrice,
referenceNormalizationFactor
);
uint256 toReturn = _calculateAndUpdateTokenAmount(amount, latestPrice, normalizationFactor, roundPrice);
if (toReturn < minAmountToken) {
revert UnexpectedPriceDifference();
}
claims[recipient][round] += toReturn;
if (token == ETH) {
payable(fundsWallets[0]).sendValue(msg.value);
} else {
token.safeTransferFrom(claimsContract, fundsWallets[0], amount);
}
emit PurchasedWithClaimAmount({
by: recipient,
amount: amount,
token: token,
round: round,
tokenPrice: latestPrice,
tokenPurchased: toReturn
});
}
function getLatestPrice(IERC20 token) public view returns (TokenInfo memory) {
PriceFeedData memory data = tokenData[token];
TokenInfo memory tokenInfo;
if (address(data.priceFeed) == address(0)) {
return tokenInfo;
}
(
uint80 roundId,
int price ,
,
uint256 updatedAt,
) = data.priceFeed.latestRoundData();
if (roundId == 0) {
revert RoundIdNotUpdated();
}
if (updatedAt == 0 || block.timestamp - updatedAt > data.tolerance) {
revert PriceNotUpdated();
}
return
TokenInfo({
latestPrice: uint256(price),
normalizationFactorForToken: data.normalizationFactorForToken,
normalizationFactorForNFT: data.normalizationFactorForNFT
});
}
function _checkValue(uint256 value) private pure {
if (value == 0) {
revert ZeroValue();
}
}
function _validatePurchase(uint32 round, uint256 deadline, IERC20 token) private view {
if (block.timestamp > deadline) {
revert DeadlineExpired();
}
_checkBlacklist(msg.sender);
if (!allowedTokens[round][token].access) {
revert TokenDisallowed();
}
_verifyInRound(round);
}
function _verifyCode(string memory code, uint256 deadline, uint8 v, bytes32 r, bytes32 s) private view {
bytes32 encodedMessageHash = keccak256(abi.encodePacked(msg.sender, code, deadline));
_verifyMessage(encodedMessageHash, v, r, s);
}
function _verifyCodeWithPrice(
string memory code,
uint256 deadline,
uint256 referenceTokenPrice,
IERC20 token,
uint256 normalizationFactor,
uint8 v,
bytes32 r,
bytes32 s
) private view {
bytes32 encodedMessageHash = keccak256(
abi.encodePacked(msg.sender, code, referenceTokenPrice, deadline, token, normalizationFactor)
);
_verifyMessage(encodedMessageHash, v, r, s);
}
function _verifyMessage(bytes32 encodedMessageHash, uint8 v, bytes32 r, bytes32 s) private view {
if (signerWallet != ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(encodedMessageHash), v, r, s)) {
revert InvalidSignature();
}
}
function _processPurchaseNFT(
IERC20 token,
uint256 price,
uint256 normalizationFactor,
uint32 round,
uint256[] calldata indexes,
uint256[] calldata nftAmounts,
uint256[] memory nftPrices
) private returns (uint256, uint256) {
uint256 value = 0;
uint256 totalNFTPrices = 0;
for (uint256 i = 0; i < nftPrices.length; ++i) {
uint256 nfts = nftAmounts[i];
uint256 prices = nftPrices[i];
value += (nfts * prices * (10 ** (normalizationFactor))) / price;
totalNFTPrices += nfts * prices;
}
uint256 roundPrice = _getRoundPriceForToken(msg.sender, indexes, round, token);
uint256 incentiveTokens = (totalNFTPrices * NORMALIZARION_FACTOR) / roundPrice;
_updateTokenPurchases(incentiveTokens);
ClaimNFT memory amounts = ClaimNFT({ nftAmounts: nftAmounts, roundPrice: roundPrice });
claimNFT[msg.sender][round].push(amounts);
return (value, roundPrice);
}
function _checkBlacklist(address which) private view {
if (blacklistAddress[which]) {
revert Blacklisted();
}
}
function _updateTokenPurchases(uint256 newPurchase) private {
totalPurchases += newPurchase;
}
function _validatePurchaseWithETH(
uint256 amount,
uint32 round,
uint256 deadline,
string memory code,
uint8 v,
bytes32 r,
bytes32 s
) private view {
_checkValue(amount);
_validatePurchase(round, deadline, ETH);
_verifyCode(code, deadline, v, r, s);
}
function _validatePurchaseWithToken(
IERC20 token,
uint32 round,
uint256 deadline,
string memory code,
uint256 referenceTokenPrice,
uint256 normalizationFactor,
uint8 v,
bytes32 r,
bytes32 s
) private view {
_validatePurchase(round, deadline, token);
_verifyCodeWithPrice(code, deadline, referenceTokenPrice, token, normalizationFactor, v, r, s);
}
function _getRoundPriceForToken(
address user,
uint256[] memory indexes,
uint32 round,
IERC20 token
) private view returns (uint256) {
uint256 customPrice = allowedTokens[round][token].customPrice;
uint256 roundPrice = customPrice > 0 ? customPrice : rounds[round].price;
uint256 lockedAmount;
uint256 indexLength = indexes.length;
if (indexLength == 0) {
return roundPrice;
}
for (uint256 i; i < indexLength; ++i) {
if (indexLength != i + 1) {
if (indexes[i] >= indexes[i + 1]) {
revert ArrayNotSorted();
}
}
(uint256 amount, ) = lockup.stakes(user, indexes[i]);
lockedAmount += amount;
if (lockedAmount >= MIN_LOCKED_AMOUNT) {
if (round == 1) {
roundPrice -= ((roundPrice * FIRST_ROUND) / PPH);
} else {
roundPrice -= ((roundPrice * OTHER_ROUND) / PPH);
}
break;
}
}
return roundPrice;
}
function _calculateAndUpdateTokenAmount(
uint256 purchaseAmount,
uint256 referenceTokenPrice,
uint256 normalizationFactor,
uint256 roundPrice
) private returns (uint256) {
uint256 toReturn = (purchaseAmount * referenceTokenPrice * (10 ** normalizationFactor)) / roundPrice;
_updateTokenPurchases(toReturn);
return toReturn;
}
function _validatePrice(
IERC20 token,
uint256 referenceTokenPrice,
uint8 referenceNormalizationFactor
) private view returns (uint256, uint8) {
TokenInfo memory tokenInfo = getLatestPrice(token);
if (tokenInfo.latestPrice != 0) {
if (referenceTokenPrice != 0 || referenceNormalizationFactor != 0) {
revert CodeSyncIssue();
}
}
if (tokenInfo.latestPrice == 0) {
if (referenceTokenPrice == 0 || referenceNormalizationFactor == 0) {
revert ZeroValue();
}
tokenInfo.latestPrice = referenceTokenPrice;
tokenInfo.normalizationFactorForToken = referenceNormalizationFactor;
}
return (tokenInfo.latestPrice, tokenInfo.normalizationFactorForToken);
}
function _sendETH(uint256 amount) private {
uint256 fundsWalletsLength = fundsWallets.length;
address[] memory wallets = fundsWallets;
for (uint256 i; i < fundsWalletsLength; ++i) {
payable(wallets[i]).sendValue(amount);
}
}
function _sendToken(IERC20 token, uint256 amount) private {
uint256 fundsWalletsLength = fundsWallets.length;
address[] memory wallets = fundsWallets;
for (uint256 i; i < fundsWalletsLength; ++i) {
token.safeTransferFrom(msg.sender, wallets[i], amount);
}
}
function _calculateTransferAmount(uint256 amount) private view returns (uint256 amountEach, uint256 equivalence) {
uint256 fundsWalletLength = fundsWallets.length;
amountEach = amount / (fundsWalletLength + 1);
equivalence = amount - (amountEach * fundsWalletLength);
}
function _verifyFundsWallets(address[] memory fundsWalletAddresses) private pure {
uint256 fundsWalletsLength = fundsWalletAddresses.length;
if (fundsWalletsLength == 0) {
revert InvalidData();
}
for (uint256 i; i < fundsWalletsLength; ++i) {
if (fundsWalletAddresses[i] == address(0)) {
revert ZeroAddress();
}
if (fundsWalletsLength != i + 1) {
if (uint256(uint160(fundsWalletAddresses[i])) >= uint256(uint160(fundsWalletAddresses[i + 1]))) {
revert InvalidValue();
}
}
}
}
}
文件 15 的 20:ReentrancyGuard.sol
pragma solidity ^0.8.20;
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
_status = ENTERED;
}
function _nonReentrantAfter() private {
_status = NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
文件 16 的 20:Rounds.sol
pragma solidity 0.8.25;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable, Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { TokenRegistry } from "./TokenRegistry.sol";
import { IRounds } from "./IRounds.sol";
import { ZeroAddress, ArrayLengthMismatch, ZeroLengthArray } from "./Common.sol";
abstract contract Rounds is IRounds, Ownable2Step, TokenRegistry {
struct AllowedToken {
bool access;
uint256 customPrice;
}
struct RoundData {
uint256 startTime;
uint256 endTime;
uint256 price;
}
uint32 internal immutable _startRound;
uint32 internal _roundIndex;
mapping(uint32 => mapping(IERC20 => AllowedToken)) public allowedTokens;
mapping(uint32 => RoundData) public rounds;
event RoundCreated(uint32 indexed newRound, RoundData roundData);
event RoundUpdated(uint32 indexed round, RoundData roundData);
event TokensAccessUpdated(uint32 indexed round, IERC20 indexed token, bool indexed access, uint256 customPrice);
error RoundNotStarted();
error RoundEnded();
error IncorrectRound();
error PriceLessThanOldRound();
error InvalidStartTime();
error InvalidEndTime();
error PriceInvalid();
error IncorrectStartTime();
error IncorrectEndTime();
error PriceGreaterThanNextRound();
error TokenDisallowed();
constructor(uint32 lastRound) {
_startRound = lastRound;
_roundIndex = lastRound;
}
function createNewRound(uint256 startTime, uint256 endTime, uint256 price) external onlyOwner {
RoundData memory prevRoundData = rounds[_roundIndex];
uint32 newRound = ++_roundIndex;
if (price < prevRoundData.price) {
revert PriceLessThanOldRound();
}
if (startTime < prevRoundData.endTime) {
revert InvalidStartTime();
}
_verifyRound(startTime, endTime, price);
prevRoundData = RoundData({ startTime: startTime, endTime: endTime, price: price });
rounds[newRound] = prevRoundData;
emit RoundCreated({ newRound: newRound, roundData: prevRoundData });
}
function updateAllowedTokens(
uint32 round,
IERC20[] calldata tokens,
bool[] calldata accesses,
uint256[] calldata customPrices
) external onlyOwner {
uint256 tokensLength = tokens.length;
if (tokensLength == 0) {
revert ZeroLengthArray();
}
if (tokensLength != accesses.length || accesses.length != customPrices.length) {
revert ArrayLengthMismatch();
}
mapping(IERC20 => AllowedToken) storage selectedRound = allowedTokens[round];
for (uint256 i = 0; i < tokensLength; ++i) {
IERC20 token = tokens[i];
if (address(token) == address(0)) {
revert ZeroAddress();
}
AllowedToken memory allowedToken = AllowedToken({ access: accesses[i], customPrice: customPrices[i] });
selectedRound[token] = allowedToken;
emit TokensAccessUpdated({
round: round,
token: token,
access: allowedToken.access,
customPrice: allowedToken.customPrice
});
}
}
function updateRound(uint32 round, uint256 startTime, uint256 endTime, uint256 price) external onlyOwner {
if (round <= _startRound || round > _roundIndex) {
revert IncorrectRound();
}
RoundData memory previousRound = rounds[round - 1];
RoundData memory nextRound = rounds[round + 1];
if (startTime < previousRound.endTime) {
revert IncorrectStartTime();
}
if (round != _roundIndex && endTime > nextRound.startTime) {
revert IncorrectEndTime();
}
if (price < previousRound.price) {
revert PriceLessThanOldRound();
}
if (round != _roundIndex && price > nextRound.price) {
revert PriceGreaterThanNextRound();
}
_verifyRound(startTime, endTime, price);
rounds[round] = RoundData({ startTime: startTime, endTime: endTime, price: price });
emit RoundUpdated({ round: round, roundData: rounds[round] });
}
function getRoundCount() external view returns (uint32) {
return _roundIndex;
}
function _validateArrays(uint256 firstLength, uint256 secondLength) internal pure {
if (firstLength == 0) {
revert ZeroLengthArray();
}
if (firstLength != secondLength) {
revert ArrayLengthMismatch();
}
}
function _verifyInRound(uint32 round) internal view {
RoundData memory dataRound = rounds[round];
if (block.timestamp < dataRound.startTime) {
revert RoundNotStarted();
}
if (block.timestamp >= dataRound.endTime) {
revert RoundEnded();
}
}
function _verifyRound(uint256 startTime, uint256 endTime, uint256 price) internal view {
if (startTime < block.timestamp) {
revert InvalidStartTime();
}
if (endTime <= startTime) {
revert InvalidEndTime();
}
if (price == 0) {
revert PriceInvalid();
}
}
}
文件 17 的 20:SafeERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
error SafeERC20FailedOperation(address token);
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
文件 18 的 20:SignedMath.sol
pragma solidity ^0.8.20;
library SignedMath {
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
function average(int256 a, int256 b) internal pure returns (int256) {
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
function abs(int256 n) internal pure returns (uint256) {
unchecked {
return uint256(n >= 0 ? n : -n);
}
}
}
文件 19 的 20:Strings.sol
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
error StringsInsufficientHexLength(uint256 value, uint256 length);
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
文件 20 的 20:TokenRegistry.sol
pragma solidity 0.8.25;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable, Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import { ZeroAddress, ArrayLengthMismatch, ZeroLengthArray, IdenticalValue } from "./Common.sol";
abstract contract TokenRegistry is Ownable2Step {
struct PriceFeedData {
AggregatorV3Interface priceFeed;
uint8 normalizationFactorForToken;
uint8 normalizationFactorForNFT;
uint256 tolerance;
}
mapping(IERC20 => PriceFeedData) public tokenData;
event TokenDataAdded(IERC20 token, PriceFeedData data);
function setTokenPriceFeed(IERC20[] calldata tokens, PriceFeedData[] calldata priceFeedData) external onlyOwner {
uint256 tokensLength = tokens.length;
if (tokensLength == 0) {
revert ZeroLengthArray();
}
if (tokensLength != priceFeedData.length) {
revert ArrayLengthMismatch();
}
for (uint256 i = 0; i < tokensLength; ++i) {
PriceFeedData calldata data = priceFeedData[i];
IERC20 token = tokens[i];
PriceFeedData memory currentPriceFeedData = tokenData[token];
if (address(token) == address(0) || address(data.priceFeed) == address(0)) {
revert ZeroAddress();
}
if (
currentPriceFeedData.priceFeed == data.priceFeed &&
currentPriceFeedData.normalizationFactorForToken == data.normalizationFactorForToken &&
currentPriceFeedData.normalizationFactorForNFT == data.normalizationFactorForNFT &&
currentPriceFeedData.tolerance == data.tolerance
) {
revert IdenticalValue();
}
emit TokenDataAdded({ token: token, data: data });
tokenData[token] = data;
}
}
}
{
"compilationTarget": {
"contracts/PreSale.sol": "PreSale"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000000
},
"remappings": [],
"viaIR": true
}
[{"inputs":[{"internalType":"address[]","name":"fundsWalletAddresses","type":"address[]"},{"internalType":"address","name":"signerAddress","type":"address"},{"internalType":"address","name":"claimsContractAddress","type":"address"},{"internalType":"contract ILockup","name":"lockupContractAddress","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint32","name":"lastRound","type":"uint32"},{"internalType":"uint256[]","name":"nftPrices","type":"uint256[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"ArrayNotSorted","type":"error"},{"inputs":[],"name":"Blacklisted","type":"error"},{"inputs":[],"name":"BuyNotEnable","type":"error"},{"inputs":[],"name":"CodeSyncIssue","type":"error"},{"inputs":[],"name":"DeadlineExpired","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"IdenticalValue","type":"error"},{"inputs":[],"name":"IncorrectEndTime","type":"error"},{"inputs":[],"name":"IncorrectRound","type":"error"},{"inputs":[],"name":"IncorrectStartTime","type":"error"},{"inputs":[],"name":"InvalidData","type":"error"},{"inputs":[],"name":"InvalidEndTime","type":"error"},{"inputs":[],"name":"InvalidPurchase","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidStartTime","type":"error"},{"inputs":[],"name":"InvalidValue","type":"error"},{"inputs":[],"name":"OnlyClaims","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"PriceGreaterThanNextRound","type":"error"},{"inputs":[],"name":"PriceInvalid","type":"error"},{"inputs":[],"name":"PriceLessThanOldRound","type":"error"},{"inputs":[],"name":"PriceNotFound","type":"error"},{"inputs":[],"name":"PriceNotUpdated","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"RoundEnded","type":"error"},{"inputs":[],"name":"RoundIdNotUpdated","type":"error"},{"inputs":[],"name":"RoundNotStarted","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"TokenDisallowed","type":"error"},{"inputs":[],"name":"UnexpectedPriceDifference","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroLengthArray","type":"error"},{"inputs":[],"name":"ZeroValue","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"which","type":"address"},{"indexed":false,"internalType":"bool","name":"accessNow","type":"bool"}],"name":"BlacklistUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"oldAccess","type":"bool"},{"indexed":false,"internalType":"bool","name":"newAccess","type":"bool"}],"name":"BuyEnableUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address[]","name":"oldAddresses","type":"address[]"},{"indexed":true,"internalType":"address[]","name":"newAddresses","type":"address[]"}],"name":"FundsWalletsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","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"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newPrice","type":"uint256"}],"name":"PricingUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"contract IERC20","name":"token","type":"address"},{"indexed":true,"internalType":"uint32","name":"round","type":"uint32"},{"indexed":true,"internalType":"uint256","name":"tokenPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenPurchased","type":"uint256"}],"name":"PurchasedWithClaimAmount","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"string","name":"code","type":"string"},{"indexed":false,"internalType":"uint256","name":"amountPurchasedETH","type":"uint256"},{"indexed":true,"internalType":"uint32","name":"round","type":"uint32"},{"indexed":true,"internalType":"uint256","name":"roundPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenPurchased","type":"uint256"}],"name":"PurchasedWithETH","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"string","name":"code","type":"string"},{"indexed":false,"internalType":"uint256","name":"amountInETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ethPrice","type":"uint256"},{"indexed":true,"internalType":"uint32","name":"round","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"roundPrice","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"nftAmounts","type":"uint256[]"}],"name":"PurchasedWithETHForNFT","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IERC20","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenPrice","type":"uint256"},{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"string","name":"code","type":"string"},{"indexed":false,"internalType":"uint256","name":"amountPurchased","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenPurchased","type":"uint256"},{"indexed":true,"internalType":"uint32","name":"round","type":"uint32"}],"name":"PurchasedWithToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IERC20","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenPrice","type":"uint256"},{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"string","name":"code","type":"string"},{"indexed":false,"internalType":"uint256","name":"amountPurchased","type":"uint256"},{"indexed":true,"internalType":"uint32","name":"round","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"roundPrice","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"nftAmounts","type":"uint256[]"}],"name":"PurchasedWithTokenForNFT","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"newRound","type":"uint32"},{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"indexed":false,"internalType":"struct Rounds.RoundData","name":"roundData","type":"tuple"}],"name":"RoundCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"round","type":"uint32"},{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"indexed":false,"internalType":"struct Rounds.RoundData","name":"roundData","type":"tuple"}],"name":"RoundUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldSigner","type":"address"},{"indexed":false,"internalType":"address","name":"newSigner","type":"address"}],"name":"SignerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IERC20","name":"token","type":"address"},{"components":[{"internalType":"contract AggregatorV3Interface","name":"priceFeed","type":"address"},{"internalType":"uint8","name":"normalizationFactorForToken","type":"uint8"},{"internalType":"uint8","name":"normalizationFactorForNFT","type":"uint8"},{"internalType":"uint256","name":"tolerance","type":"uint256"}],"indexed":false,"internalType":"struct TokenRegistry.PriceFeedData","name":"data","type":"tuple"}],"name":"TokenDataAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"round","type":"uint32"},{"indexed":true,"internalType":"contract IERC20","name":"token","type":"address"},{"indexed":true,"internalType":"bool","name":"access","type":"bool"},{"indexed":false,"internalType":"uint256","name":"customPrice","type":"uint256"}],"name":"TokensAccessUpdated","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"contract IERC20","name":"","type":"address"}],"name":"allowedTokens","outputs":[{"internalType":"bool","name":"access","type":"bool"},{"internalType":"uint256","name":"customPrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"blacklistAddress","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"buyEnable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"newFundsWallets","type":"address[]"}],"name":"changeFundsWallets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newSigner","type":"address"}],"name":"changeSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"claimNFT","outputs":[{"internalType":"uint256","name":"roundPrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint32","name":"","type":"uint32"}],"name":"claims","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimsContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"createNewRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled","type":"bool"}],"name":"enableBuy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"fundsWallets","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"}],"name":"getLatestPrice","outputs":[{"components":[{"internalType":"uint256","name":"latestPrice","type":"uint256"},{"internalType":"uint8","name":"normalizationFactorForToken","type":"uint8"},{"internalType":"uint8","name":"normalizationFactorForNFT","type":"uint8"}],"internalType":"struct PreSale.TokenInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"buyer","type":"address"},{"internalType":"uint32","name":"round","type":"uint32"}],"name":"getNFTClaims","outputs":[{"components":[{"internalType":"uint256[]","name":"nftAmounts","type":"uint256[]"},{"internalType":"uint256","name":"roundPrice","type":"uint256"}],"internalType":"struct PreSale.ClaimNFT[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRoundCount","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockup","outputs":[{"internalType":"contract ILockup","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"nftPricing","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"code","type":"string"},{"internalType":"uint32","name":"round","type":"uint32"},{"internalType":"uint256[]","name":"nftAmounts","type":"uint256[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"purchaseNFTWithETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"referenceTokenPrice","type":"uint256"},{"internalType":"uint8","name":"referenceNormalizationFactor","type":"uint8"},{"internalType":"string","name":"code","type":"string"},{"internalType":"uint32","name":"round","type":"uint32"},{"internalType":"uint256[]","name":"nftAmounts","type":"uint256[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"purchaseNFTWithToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"code","type":"string"},{"internalType":"uint32","name":"round","type":"uint32"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"minAmountToken","type":"uint256"},{"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"purchaseTokenWithETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint8","name":"referenceNormalizationFactor","type":"uint8"},{"internalType":"uint256","name":"referenceTokenPrice","type":"uint256"},{"internalType":"uint256","name":"purchaseAmount","type":"uint256"},{"internalType":"uint256","name":"minAmountToken","type":"uint256"},{"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"internalType":"string","name":"code","type":"string"},{"internalType":"uint32","name":"round","type":"uint32"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"purchaseTokenWithToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"referenceTokenPrice","type":"uint256"},{"internalType":"uint8","name":"referenceNormalizationFactor","type":"uint8"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minAmountToken","type":"uint256"},{"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint32","name":"round","type":"uint32"}],"name":"purchaseWithClaim","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"rounds","outputs":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20[]","name":"tokens","type":"address[]"},{"components":[{"internalType":"contract AggregatorV3Interface","name":"priceFeed","type":"address"},{"internalType":"uint8","name":"normalizationFactorForToken","type":"uint8"},{"internalType":"uint8","name":"normalizationFactorForNFT","type":"uint8"},{"internalType":"uint256","name":"tolerance","type":"uint256"}],"internalType":"struct TokenRegistry.PriceFeedData[]","name":"priceFeedData","type":"tuple[]"}],"name":"setTokenPriceFeed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"signerWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"name":"tokenData","outputs":[{"internalType":"contract AggregatorV3Interface","name":"priceFeed","type":"address"},{"internalType":"uint8","name":"normalizationFactorForToken","type":"uint8"},{"internalType":"uint8","name":"normalizationFactorForNFT","type":"uint8"},{"internalType":"uint256","name":"tolerance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalPurchases","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"round","type":"uint32"},{"internalType":"contract IERC20[]","name":"tokens","type":"address[]"},{"internalType":"bool[]","name":"accesses","type":"bool[]"},{"internalType":"uint256[]","name":"customPrices","type":"uint256[]"}],"name":"updateAllowedTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"which","type":"address"},{"internalType":"bool","name":"access","type":"bool"}],"name":"updateBlackListedUser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"newPrices","type":"uint256[]"}],"name":"updatePricing","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"round","type":"uint32"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"updateRound","outputs":[],"stateMutability":"nonpayable","type":"function"}]