Transaction Hash:
Block:
21623105 at Jan-14-2025 01:44:35 PM +UTC
Transaction Fee:
0.00239415102066 ETH
$6.06
Gas Used:
249,000 Gas / 9.61506434 Gwei
Emitted Events:
48 |
MicroGPT.Transfer( from=[Receiver] RewardDistributor, to=[Sender] 0x1e830ed61b6f1bb785481de18b06283d0736b955, value=5652413527727804273166 )
|
49 |
RewardDistributor.TokensClaimed( user=[Sender] 0x1e830ed61b6f1bb785481de18b06283d0736b955, token=MicroGPT, amount=5652413527727804273166, userTokenTimeCursor=1736380800 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x1e830ED6...D0736B955 |
0.194451790784185588 Eth
Nonce: 670
|
0.192057639763525588 Eth
Nonce: 671
| 0.00239415102066 | ||
0x4fFAC630...61DF3620d | (Aethir: Reward Distributor ATH - AI) | ||||
0x8CEDb068...b7fDC26d5 | |||||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 19.981650135470598274 Eth | 19.982025908465853274 Eth | 0.000375772995255 |
Execution Trace
RewardDistributor.claimTokens( user=0x1e830ED61b6f1bB785481dE18B06283D0736B955, tokens=[0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5, 0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B] ) => ( [5652413527727804273166, 0] )
RewardDistributor.claimTokens( user=0x1e830ED61b6f1bB785481dE18B06283D0736B955, tokens=[0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5, 0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B] ) => ( [5652413527727804273166, 0] )
Escrow.user_point_epoch( arg0=0x1e830ED61b6f1bB785481dE18B06283D0736B955 ) => ( 1 )
-
Escrow.user_point_epoch( arg0=0x1e830ED61b6f1bB785481dE18B06283D0736B955 ) => ( 1 )
-
MicroGPT.transfer( recipient=0x1e830ED61b6f1bB785481dE18B06283D0736B955, amount=5652413527727804273166 ) => ( True )
-
0x5db962d3beb22659803376af721293f22c30aba7.ecd07a53( )
-
RewardFaucet.distributePastRewards( token=0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5 )
-
RewardFaucet.distributePastRewards( token=0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5 )
-
RewardFaucet.distributePastRewards( token=0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B )
-
RewardFaucet.distributePastRewards( token=0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B )
-
claimTokens[RewardDistributor (ln:1711)]
_checkpointTotalSupply[RewardDistributor (ln:1721)]
_roundDownTimestamp[RewardDistributor (ln:2014)]
checkpoint[RewardDistributor (ln:2020)]
totalSupply[RewardDistributor (ln:2024)]
_checkpointUserBalance[RewardDistributor (ln:1722)]
user_point_epoch[RewardDistributor (ln:1890)]
_findTimestampUserEpoch[RewardDistributor (ln:1901)]
user_point_history[RewardDistributor (ln:2066)]
_findTimestampUserEpoch[RewardDistributor (ln:1923)]
user_point_history[RewardDistributor (ln:2066)]
user_point_history[RewardDistributor (ln:1935)]
max[RewardDistributor (ln:1946)]
_roundUpTimestamp[RewardDistributor (ln:1948)]
_roundDownTimestamp[RewardDistributor (ln:2093)]
Point[RewardDistributor (ln:1967)]
user_point_history[RewardDistributor (ln:1969)]
_roundUpTimestamp[RewardDistributor (ln:1992)]
_roundDownTimestamp[RewardDistributor (ln:2093)]
_checkpointToken[RewardDistributor (ln:1727)]
_roundDownTimestamp[RewardDistributor (ln:1797)]
_roundDownTimestamp[RewardDistributor (ln:1809)]
_roundDownTimestamp[RewardDistributor (ln:1811)]
_roundUpTimestamp[RewardDistributor (ln:1814)]
_roundDownTimestamp[RewardDistributor (ln:2093)]
balanceOf[RewardDistributor (ln:1825)]
sub[RewardDistributor (ln:1826)]
type[RewardDistributor (ln:1831)]
_roundDownTimestamp[RewardDistributor (ln:1834)]
add[RewardDistributor (ln:1859)]
type[RewardDistributor (ln:1859)]
add[RewardDistributor (ln:1876)]
type[RewardDistributor (ln:1876)]
TokenCheckpointed[RewardDistributor (ln:1884)]
_claimToken[RewardDistributor (ln:1728)]
_getUserTokenTimeCursor[RewardDistributor (ln:1743)]
min[RewardDistributor (ln:1752)]
_roundUpTimestamp[RewardDistributor (ln:1753)]
_roundDownTimestamp[RewardDistributor (ln:2093)]
min[RewardDistributor (ln:1754)]
_roundDownTimestamp[RewardDistributor (ln:1756)]
safeTransfer[RewardDistributor (ln:1781)]
TokensClaimed[RewardDistributor (ln:1782)]
distributePastRewards[RewardDistributor (ln:1729)]
File 1 of 7: RewardDistributor
File 2 of 7: MicroGPT
File 3 of 7: RewardDistributor
File 4 of 7: Voting Escrow
File 5 of 7: Voting Escrow
File 6 of 7: RewardFaucet
File 7 of 7: RewardFaucet
// SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; // solhint-disable /** * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are * supported. * Uses the default 'BAL' prefix for the error code */ function _require(bool condition, uint256 errorCode) pure { if (!condition) _revert(errorCode); } /** * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are * supported. */ function _require( bool condition, uint256 errorCode, bytes3 prefix ) pure { if (!condition) _revert(errorCode, prefix); } /** * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported. * Uses the default 'BAL' prefix for the error code */ function _revert(uint256 errorCode) pure { _revert(errorCode, 0x42414c); // This is the raw byte representation of "BAL" } /** * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported. */ function _revert(uint256 errorCode, bytes3 prefix) pure { uint256 prefixUint = uint256(uint24(prefix)); // We're going to dynamically create a revert string based on the error code, with the following format: // 'BAL#{errorCode}' // where the code is left-padded with zeroes to three digits (so they range from 000 to 999). // // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a // number (8 to 16 bits) than the individual string characters. // // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a // safe place to rely on it without worrying about how its usage might affect e.g. memory contents. assembly { // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999 // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for // the '0' character. let units := add(mod(errorCode, 10), 0x30) errorCode := div(errorCode, 10) let tenths := add(mod(errorCode, 10), 0x30) errorCode := div(errorCode, 10) let hundreds := add(mod(errorCode, 10), 0x30) // With the individual characters, we can now construct the full string. // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#') // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the // characters to it, each shifted by a multiple of 8. // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte // array). let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint))) let revertReason := shl(200, add(formattedPrefix, add(add(units, shl(8, tenths)), shl(16, hundreds)))) // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded // message will have the following layout: // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ] // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten. mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away). mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) // The string length is fixed: 7 characters. mstore(0x24, 7) // Finally, the string itself is stored. mstore(0x44, revertReason) // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of // the encoded message is therefore 4 + 32 + 32 + 32 = 100. revert(0, 100) } } library Errors { // Math uint256 internal constant ADD_OVERFLOW = 0; uint256 internal constant SUB_OVERFLOW = 1; uint256 internal constant SUB_UNDERFLOW = 2; uint256 internal constant MUL_OVERFLOW = 3; uint256 internal constant ZERO_DIVISION = 4; uint256 internal constant DIV_INTERNAL = 5; uint256 internal constant X_OUT_OF_BOUNDS = 6; uint256 internal constant Y_OUT_OF_BOUNDS = 7; uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8; uint256 internal constant INVALID_EXPONENT = 9; // Input uint256 internal constant OUT_OF_BOUNDS = 100; uint256 internal constant UNSORTED_ARRAY = 101; uint256 internal constant UNSORTED_TOKENS = 102; uint256 internal constant INPUT_LENGTH_MISMATCH = 103; uint256 internal constant ZERO_TOKEN = 104; uint256 internal constant INSUFFICIENT_DATA = 105; // Shared pools uint256 internal constant MIN_TOKENS = 200; uint256 internal constant MAX_TOKENS = 201; uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202; uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203; uint256 internal constant MINIMUM_BPT = 204; uint256 internal constant CALLER_NOT_VAULT = 205; uint256 internal constant UNINITIALIZED = 206; uint256 internal constant BPT_IN_MAX_AMOUNT = 207; uint256 internal constant BPT_OUT_MIN_AMOUNT = 208; uint256 internal constant EXPIRED_PERMIT = 209; uint256 internal constant NOT_TWO_TOKENS = 210; uint256 internal constant DISABLED = 211; // Pools uint256 internal constant MIN_AMP = 300; uint256 internal constant MAX_AMP = 301; uint256 internal constant MIN_WEIGHT = 302; uint256 internal constant MAX_STABLE_TOKENS = 303; uint256 internal constant MAX_IN_RATIO = 304; uint256 internal constant MAX_OUT_RATIO = 305; uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306; uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307; uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308; uint256 internal constant INVALID_TOKEN = 309; uint256 internal constant UNHANDLED_JOIN_KIND = 310; uint256 internal constant ZERO_INVARIANT = 311; uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312; uint256 internal constant ORACLE_NOT_INITIALIZED = 313; uint256 internal constant ORACLE_QUERY_TOO_OLD = 314; uint256 internal constant ORACLE_INVALID_INDEX = 315; uint256 internal constant ORACLE_BAD_SECS = 316; uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317; uint256 internal constant AMP_ONGOING_UPDATE = 318; uint256 internal constant AMP_RATE_TOO_HIGH = 319; uint256 internal constant AMP_NO_ONGOING_UPDATE = 320; uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321; uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322; uint256 internal constant RELAYER_NOT_CONTRACT = 323; uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324; uint256 internal constant REBALANCING_RELAYER_REENTERED = 325; uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326; uint256 internal constant SWAPS_DISABLED = 327; uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328; uint256 internal constant PRICE_RATE_OVERFLOW = 329; uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330; uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331; uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332; uint256 internal constant UPPER_TARGET_TOO_HIGH = 333; uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334; uint256 internal constant OUT_OF_TARGET_RANGE = 335; uint256 internal constant UNHANDLED_EXIT_KIND = 336; uint256 internal constant UNAUTHORIZED_EXIT = 337; uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338; uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339; uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340; uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341; uint256 internal constant INVALID_INITIALIZATION = 342; uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343; uint256 internal constant FEATURE_DISABLED = 344; uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345; uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346; uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347; uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348; uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349; uint256 internal constant MAX_WEIGHT = 350; uint256 internal constant UNAUTHORIZED_JOIN = 351; uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352; uint256 internal constant FRACTIONAL_TARGET = 353; uint256 internal constant ADD_OR_REMOVE_BPT = 354; uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355; uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356; uint256 internal constant MALICIOUS_QUERY_REVERT = 357; uint256 internal constant JOINS_EXITS_DISABLED = 358; // Lib uint256 internal constant REENTRANCY = 400; uint256 internal constant SENDER_NOT_ALLOWED = 401; uint256 internal constant PAUSED = 402; uint256 internal constant PAUSE_WINDOW_EXPIRED = 403; uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404; uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405; uint256 internal constant INSUFFICIENT_BALANCE = 406; uint256 internal constant INSUFFICIENT_ALLOWANCE = 407; uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408; uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409; uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410; uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411; uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412; uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413; uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414; uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415; uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416; uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417; uint256 internal constant SAFE_ERC20_CALL_FAILED = 418; uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419; uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420; uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421; uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422; uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423; uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424; uint256 internal constant BUFFER_PERIOD_EXPIRED = 425; uint256 internal constant CALLER_IS_NOT_OWNER = 426; uint256 internal constant NEW_OWNER_IS_ZERO = 427; uint256 internal constant CODE_DEPLOYMENT_FAILED = 428; uint256 internal constant CALL_TO_NON_CONTRACT = 429; uint256 internal constant LOW_LEVEL_CALL_FAILED = 430; uint256 internal constant NOT_PAUSED = 431; uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432; uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433; uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434; uint256 internal constant INVALID_OPERATION = 435; uint256 internal constant CODEC_OVERFLOW = 436; uint256 internal constant IN_RECOVERY_MODE = 437; uint256 internal constant NOT_IN_RECOVERY_MODE = 438; uint256 internal constant INDUCED_FAILURE = 439; uint256 internal constant EXPIRED_SIGNATURE = 440; uint256 internal constant MALFORMED_SIGNATURE = 441; uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442; uint256 internal constant UNHANDLED_FEE_TYPE = 443; uint256 internal constant BURN_FROM_ZERO = 444; // Vault uint256 internal constant INVALID_POOL_ID = 500; uint256 internal constant CALLER_NOT_POOL = 501; uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502; uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503; uint256 internal constant INVALID_SIGNATURE = 504; uint256 internal constant EXIT_BELOW_MIN = 505; uint256 internal constant JOIN_ABOVE_MAX = 506; uint256 internal constant SWAP_LIMIT = 507; uint256 internal constant SWAP_DEADLINE = 508; uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509; uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510; uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511; uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512; uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513; uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514; uint256 internal constant INVALID_POST_LOAN_BALANCE = 515; uint256 internal constant INSUFFICIENT_ETH = 516; uint256 internal constant UNALLOCATED_ETH = 517; uint256 internal constant ETH_TRANSFER = 518; uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519; uint256 internal constant TOKENS_MISMATCH = 520; uint256 internal constant TOKEN_NOT_REGISTERED = 521; uint256 internal constant TOKEN_ALREADY_REGISTERED = 522; uint256 internal constant TOKENS_ALREADY_SET = 523; uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524; uint256 internal constant NONZERO_TOKEN_BALANCE = 525; uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526; uint256 internal constant POOL_NO_TOKENS = 527; uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528; // Fees uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600; uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601; uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602; uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603; // FeeSplitter uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700; // Misc uint256 internal constant UNIMPLEMENTED = 998; uint256 internal constant SHOULD_NOT_HAPPEN = 999; } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface for the OptionalOnlyCaller helper, used to opt in to a caller * verification for a given address to methods that are otherwise callable by any address. */ interface IOptionalOnlyCaller { /** * @dev Emitted every time setOnlyCallerCheck is called. */ event OnlyCallerOptIn(address user, bool enabled); /** * @dev Enables / disables verification mechanism for caller. * @param enabled - True if caller verification shall be enabled, false otherwise. */ function setOnlyCallerCheck(bool enabled) external; function setOnlyCallerCheckWithSignature( address user, bool enabled, bytes memory signature ) external; /** * @dev Returns true if caller verification is enabled for the given user, false otherwise. */ function isOnlyCallerEnabled(address user) external view returns (bool); } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface for the SignatureValidator helper, used to support meta-transactions. */ interface ISignaturesValidator { /** * @dev Returns the EIP712 domain separator. */ function getDomainSeparator() external view returns (bytes32); /** * @dev Returns the next nonce used by an address to sign messages. */ function getNextNonce(address user) external view returns (uint256); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface of the ERC1271 standard signature validation method for * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. * * _Available since v4.1._ */ interface IERC1271 { /** * @dev Should return whether the signature provided is valid for the provided data * @param hash Hash of the data to be signed * @param signature Signature byte array associated with _data */ function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); } // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/ISignaturesValidator.sol"; import "../openzeppelin/EIP712.sol"; /** * @dev Utility for signing Solidity function calls. */ abstract contract EOASignaturesValidator is ISignaturesValidator, EIP712 { // Replay attack prevention for each account. mapping(address => uint256) internal _nextNonce; function getDomainSeparator() public view override returns (bytes32) { return _domainSeparatorV4(); } function getNextNonce(address account) public view override returns (uint256) { return _nextNonce[account]; } function _ensureValidSignature( address account, bytes32 structHash, bytes memory signature, uint256 errorCode ) internal { return _ensureValidSignature(account, structHash, signature, type(uint256).max, errorCode); } function _ensureValidSignature( address account, bytes32 structHash, bytes memory signature, uint256 deadline, uint256 errorCode ) internal { bytes32 digest = _hashTypedDataV4(structHash); _require(_isValidSignature(account, digest, signature), errorCode); // We could check for the deadline before validating the signature, but this leads to saner error processing (as // we only care about expired deadlines if the signature is correct) and only affects the gas cost of the revert // scenario, which will only occur infrequently, if ever. // The deadline is timestamp-based: it should not be relied upon for sub-minute accuracy. // solhint-disable-next-line not-rely-on-time _require(deadline >= block.timestamp, Errors.EXPIRED_SIGNATURE); // We only advance the nonce after validating the signature. This is irrelevant for this module, but it can be // important in derived contracts that override _isValidSignature (e.g. SignaturesValidator), as we want for // the observable state to still have the current nonce as the next valid one. _nextNonce[account] += 1; } function _isValidSignature( address account, bytes32 digest, bytes memory signature ) internal view virtual returns (bool) { _require(signature.length == 65, Errors.MALFORMED_SIGNATURE); bytes32 r; bytes32 s; uint8 v; // ecrecover takes the r, s and v signature parameters, and the only way to get them is to use assembly. // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } address recoveredAddress = ecrecover(digest, v, r, s); // ecrecover returns the zero address on recover failure, so we need to handle that explicitly. return (recoveredAddress != address(0) && recoveredAddress == account); } function _toArraySignature( uint8 v, bytes32 r, bytes32 s ) internal pure returns (bytes memory) { bytes memory signature = new bytes(65); // solhint-disable-next-line no-inline-assembly assembly { mstore(add(signature, 32), r) mstore(add(signature, 64), s) mstore8(add(signature, 96), v) } return signature; } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; library InputHelpers { function ensureInputLengthMatch(uint256 a, uint256 b) internal pure { _require(a == b, Errors.INPUT_LENGTH_MISMATCH); } function ensureInputLengthMatch( uint256 a, uint256 b, uint256 c ) internal pure { _require(a == b && b == c, Errors.INPUT_LENGTH_MISMATCH); } function ensureArrayIsSorted(IERC20[] memory array) internal pure { address[] memory addressArray; // solhint-disable-next-line no-inline-assembly assembly { addressArray := array } ensureArrayIsSorted(addressArray); } function ensureArrayIsSorted(address[] memory array) internal pure { if (array.length < 2) { return; } address previous = array[0]; for (uint256 i = 1; i < array.length; ++i) { address current = array[i]; _require(previous < current, Errors.UNSORTED_ARRAY); previous = current; } } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/IOptionalOnlyCaller.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; import "./SignaturesValidator.sol"; abstract contract OptionalOnlyCaller is IOptionalOnlyCaller, SignaturesValidator { mapping(address => bool) private _isOnlyCallerEnabled; bytes32 private constant _SET_ONLY_CALLER_CHECK_TYPEHASH = keccak256( "SetOnlyCallerCheck(address user,bool enabled,uint256 nonce)" ); /** * @dev Reverts if the verification mechanism is enabled and the given address is not the caller. * @param user - Address to validate as the only allowed caller, if the verification is enabled. */ modifier optionalOnlyCaller(address user) { _verifyCaller(user); _; } function setOnlyCallerCheck(bool enabled) external override { _setOnlyCallerCheck(msg.sender, enabled); } function setOnlyCallerCheckWithSignature( address user, bool enabled, bytes memory signature ) external override { bytes32 structHash = keccak256(abi.encode(_SET_ONLY_CALLER_CHECK_TYPEHASH, user, enabled, getNextNonce(user))); _ensureValidSignature(user, structHash, signature, Errors.INVALID_SIGNATURE); _setOnlyCallerCheck(user, enabled); } function _setOnlyCallerCheck(address user, bool enabled) private { _isOnlyCallerEnabled[user] = enabled; emit OnlyCallerOptIn(user, enabled); } function isOnlyCallerEnabled(address user) external view override returns (bool) { return _isOnlyCallerEnabled[user]; } function _verifyCaller(address user) private view { if (_isOnlyCallerEnabled[user]) { _require(msg.sender == user, Errors.SENDER_NOT_ALLOWED); } } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC1271.sol"; import "./EOASignaturesValidator.sol"; import "../openzeppelin/Address.sol"; /** * @dev Utility for signing Solidity function calls. */ abstract contract SignaturesValidator is EOASignaturesValidator { using Address for address; function _isValidSignature( address account, bytes32 digest, bytes memory signature ) internal view virtual override returns (bool) { if (account.isContract()) { return IERC1271(account).isValidSignature(digest, signature) == IERC1271.isValidSignature.selector; } else { return super._isValidSignature(account, digest, signature); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow checks. * Adapted from OpenZeppelin's SafeMath library. */ library Math { // solhint-disable no-inline-assembly /** * @dev Returns the absolute value of a signed integer. */ function abs(int256 a) internal pure returns (uint256 result) { // Equivalent to: // result = a > 0 ? uint256(a) : uint256(-a) assembly { let s := sar(255, a) result := sub(xor(a, s), s) } } /** * @dev Returns the addition of two unsigned integers of 256 bits, reverting on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; _require(c >= a, Errors.ADD_OVERFLOW); return c; } /** * @dev Returns the addition of two signed integers, reverting on overflow. */ function add(int256 a, int256 b) internal pure returns (int256) { int256 c = a + b; _require((b >= 0 && c >= a) || (b < 0 && c < a), Errors.ADD_OVERFLOW); return c; } /** * @dev Returns the subtraction of two unsigned integers of 256 bits, reverting on overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { _require(b <= a, Errors.SUB_OVERFLOW); uint256 c = a - b; return c; } /** * @dev Returns the subtraction of two signed integers, reverting on overflow. */ function sub(int256 a, int256 b) internal pure returns (int256) { int256 c = a - b; _require((b >= 0 && c <= a) || (b < 0 && c > a), Errors.SUB_OVERFLOW); return c; } /** * @dev Returns the largest of two numbers of 256 bits. */ function max(uint256 a, uint256 b) internal pure returns (uint256 result) { // Equivalent to: // result = (a < b) ? b : a; assembly { result := sub(a, mul(sub(a, b), lt(a, b))) } } /** * @dev Returns the smallest of two numbers of 256 bits. */ function min(uint256 a, uint256 b) internal pure returns (uint256 result) { // Equivalent to `result = (a < b) ? a : b` assembly { result := sub(a, mul(sub(a, b), gt(a, b))) } } function mul(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a * b; _require(a == 0 || c / a == b, Errors.MUL_OVERFLOW); return c; } function div( uint256 a, uint256 b, bool roundUp ) internal pure returns (uint256) { return roundUp ? divUp(a, b) : divDown(a, b); } function divDown(uint256 a, uint256 b) internal pure returns (uint256) { _require(b != 0, Errors.ZERO_DIVISION); return a / b; } function divUp(uint256 a, uint256 b) internal pure returns (uint256 result) { _require(b != 0, Errors.ZERO_DIVISION); // Equivalent to: // result = a == 0 ? 0 : 1 + (a - 1) / b; assembly { result := mul(iszero(iszero(a)), add(1, div(sub(a, 1), b))) } } } // SPDX-License-Identifier: MIT // Based on the Address library from OpenZeppelin Contracts, altered by removing the `isContract` checks on // `functionCall` and `functionDelegateCall` in order to save gas, as the recipients are known to be contracts. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } // solhint-disable max-line-length /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { _require(address(this).balance >= amount, Errors.ADDRESS_INSUFFICIENT_BALANCE); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); _require(success, Errors.ADDRESS_CANNOT_SEND_VALUE); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call(data); return verifyCallResult(success, returndata); } // solhint-enable max-line-length /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but passing some native ETH as msg.value to the call. * * _Available since v3.4._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: value }(data); return verifyCallResult(success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResult(success, returndata); } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling up the * revert reason or using the one provided. * * _Available since v4.3._ */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { _revert(Errors.LOW_LEVEL_CALL_FAILED); } } } } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. * * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding * they need in their contracts using a combination of `abi.encode` and `keccak256`. * * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA * ({_hashTypedDataV4}). * * The implementation of the domain separator was designed to be as efficient as possible while still properly updating * the chain id to protect against replay attacks on an eventual fork of the chain. * * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. * * _Available since v3.4._ */ abstract contract EIP712 { /* solhint-disable var-name-mixedcase */ bytes32 private immutable _HASHED_NAME; bytes32 private immutable _HASHED_VERSION; bytes32 private immutable _TYPE_HASH; /* solhint-enable var-name-mixedcase */ /** * @dev Initializes the domain separator and parameter caches. * * The meaning of `name` and `version` is specified in * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: * * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. * - `version`: the current major version of the signing domain. * * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart * contract upgrade]. */ constructor(string memory name, string memory version) { _HASHED_NAME = keccak256(bytes(name)); _HASHED_VERSION = keccak256(bytes(version)); _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); } /** * @dev Returns the domain separator for the current chain. */ function _domainSeparatorV4() internal view virtual returns (bytes32) { return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, _getChainId(), address(this))); } /** * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this * function returns the hash of the fully encoded EIP712 message for this domain. * * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: * * ```solidity * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( * keccak256("Mail(address to,string contents)"), * mailTo, * keccak256(bytes(mailContents)) * ))); * address signer = ECDSA.recover(digest, signature); * ``` */ function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { return keccak256(abi.encodePacked("\\x19\\x01", _domainSeparatorV4(), structHash)); } // solc-ignore-next-line func-mutability function _getChainId() private view returns (uint256 chainId) { // solhint-disable-next-line no-inline-assembly assembly { chainId := chainid() } } } // SPDX-License-Identifier: MIT // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce bytecode size. // Modifier code is inlined by the compiler, which causes its code to appear multiple times in the codebase. By using // private functions, we achieve the same end result with slightly higher runtime gas costs, but reduced bytecode size. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { _enterNonReentrant(); _; _exitNonReentrant(); } function _enterNonReentrant() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED _require(_status != _ENTERED, Errors.REENTRANCY); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _exitNonReentrant() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } } // SPDX-License-Identifier: MIT // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce gas costs. // The `safeTransfer` and `safeTransferFrom` functions assume that `token` is a contract (an account with code), and // work differently from the OpenZeppelin version if it is not. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { function safeApprove( IERC20 token, address to, uint256 value ) internal { // Some contracts need their allowance reduced to 0 before setting it to an arbitrary amount. if (value != 0 && token.allowance(address(this), address(to)) != 0) { _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, 0)); } _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, value)); } function safeTransfer( IERC20 token, address to, uint256 value ) internal { _callOptionalReturn(address(token), abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom( IERC20 token, address from, address to, uint256 value ) internal { _callOptionalReturn(address(token), abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * * WARNING: `token` is assumed to be a contract: calls to EOAs will *not* revert. */ function _callOptionalReturn(address token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = token.call(data); // If the low-level call didn't succeed we return whatever was returned from it. // solhint-disable-next-line no-inline-assembly assembly { if eq(success, 0) { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } // Finally we check the returndata size is either zero or true - note that this check will always pass for EOAs _require(returndata.length == 0 || abi.decode(returndata, (bool)), Errors.SAFE_ERC20_CALL_FAILED); } } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; _require(c >= a, Errors.ADD_OVERFLOW); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, Errors.SUB_OVERFLOW); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub( uint256 a, uint256 b, uint256 errorCode ) internal pure returns (uint256) { _require(b <= a, errorCode); uint256 c = a - b; return c; } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; import "./IVotingEscrow.sol"; /** * @title Reward Distributor * @notice Distributes any tokens transferred to the contract (e.g. Protocol rewards and any token emissions) among veBPT * holders proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract. * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`. */ interface IRewardDistributor { event TokenCheckpointed( IERC20 token, uint256 amount, uint256 lastCheckpointTimestamp ); event TokensClaimed( address user, IERC20 token, uint256 amount, uint256 userTokenTimeCursor ); event TokenAdded(address indexed token); event RewardDeposit(IERC20 token, uint256 amount); event NewAdmin(address indexed newAdmin); /** * @notice Returns the VotingEscrow (veBPT) token contract */ function getVotingEscrow() external view returns (IVotingEscrow); /** * @notice Returns the global time cursor representing the most earliest uncheckpointed week. */ function getTimeCursor() external view returns (uint256); /** * @notice Returns the user-level time cursor representing the most earliest uncheckpointed week. * @param user - The address of the user to query. */ function getUserTimeCursor(address user) external view returns (uint256); /** * @notice Returns the token-level time cursor storing the timestamp at up to which tokens have been distributed. * @param token - The ERC20 token address to query. */ function getTokenTimeCursor(IERC20 token) external view returns (uint256); /** * @notice Returns the user-level time cursor storing the timestamp of the latest token distribution claimed. * @param user - The address of the user to query. * @param token - The ERC20 token address to query. */ function getUserTokenTimeCursor( address user, IERC20 token ) external view returns (uint256); /** * @notice Returns the user's cached balance of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires `user` to have been checkpointed past `timestamp` so that their balance is cached. * @param user - The address of the user of which to read the cached balance of. * @param timestamp - The timestamp at which to read the `user`'s cached balance at. */ function getUserBalanceAtTimestamp( address user, uint256 timestamp ) external view returns (uint256); /** * @notice Returns the cached total supply of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached. * @param timestamp - The timestamp at which to read the cached total supply at. */ function getTotalSupplyAtTimestamp( uint256 timestamp ) external view returns (uint256); /** * @notice Returns the RewardDistributor's cached balance of `token`. */ function getTokenLastBalance(IERC20 token) external view returns (uint256); /** * @notice Returns the amount of `token` which the RewardDistributor received in the week beginning at `timestamp`. * @param token - The ERC20 token address to query. * @param timestamp - The timestamp corresponding to the beginning of the week of interest. */ function getTokensDistributedInWeek( IERC20 token, uint256 timestamp ) external view returns (uint256); // Depositing /** * @notice Deposits tokens to be distributed in the current week. * @dev Sending tokens directly to the RewardDistributor instead of using `depositTokens` may result in tokens being * retroactively distributed to past weeks, or for the distribution to carry over to future weeks. * * If for some reason `depositTokens` cannot be called, in order to ensure that all tokens are correctly distributed * manually call `checkpointToken` before and after the token transfer. * @param token - The ERC20 token address to distribute. * @param amount - The amount of tokens to deposit. */ function depositToken(IERC20 token, uint256 amount) external; /** * @notice Deposits tokens to be distributed in the current week. * @dev A version of `depositToken` which supports depositing multiple `tokens` at once. * See `depositToken` for more details. * @param tokens - An array of ERC20 token addresses to distribute. * @param amounts - An array of token amounts to deposit. */ function depositTokens( IERC20[] calldata tokens, uint256[] calldata amounts ) external; // Checkpointing /** * @notice Caches the total supply of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. */ function checkpoint() external; /** * @notice Caches the user's balance of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param user - The address of the user to be checkpointed. */ function checkpointUser(address user) external; /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev Any `token` balance held by the RewardDistributor above that which is returned by `getTokenLastBalance` * will be distributed evenly across the time period since `token` was last checkpointed. * * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param token - The ERC20 token address to be checkpointed. */ function checkpointToken(IERC20 token) external; /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev A version of `checkpointToken` which supports checkpointing multiple tokens. * See `checkpointToken` for more details. * @param tokens - An array of ERC20 token addresses to be checkpointed. */ function checkpointTokens(IERC20[] calldata tokens) external; // Claiming /** * @notice Claims all pending distributions of the provided token for a user. * @dev It's not necessary to explicitly checkpoint before calling this function, it will ensure the RewardDistributor * is up to date before calculating the amount of tokens to be claimed. * @param user - The user on behalf of which to claim. * @param token - The ERC20 token address to be claimed. * @return The amount of `token` sent to `user` as a result of claiming. */ function claimToken(address user, IERC20 token) external returns (uint256); /** * @notice Claims a number of tokens on behalf of a user. * @dev A version of `claimToken` which supports claiming multiple `tokens` on behalf of `user`. * See `claimToken` for more details. * @param user - The user on behalf of which to claim. * @param tokens - An array of ERC20 token addresses to be claimed. * @return An array of the amounts of each token in `tokens` sent to `user` as a result of claiming. */ function claimTokens( address user, IERC20[] calldata tokens ) external returns (uint256[] memory); } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface IRewardFaucet { function distributePastRewards(address rewardToken) external; } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; // solhint-disable func-name-mixedcase interface IVotingEscrow { struct Point { int128 bias; int128 slope; // - dweight / dt uint256 ts; uint256 blk; // block } function epoch() external view returns (uint256); function admin() external view returns (address); function future_admin() external view returns (address); function apply_smart_wallet_checker() external; function apply_transfer_ownership() external; // function balanceOf(address addr, uint256 _t) external view returns (uint256); function balanceOf( address user, uint256 timestamp ) external view returns (uint256); function balanceOfAt( address addr, uint256 _block ) external view returns (uint256); function checkpoint() external; function commit_smart_wallet_checker(address addr) external; function commit_transfer_ownership(address addr) external; function create_lock(uint256 _value, uint256 _unlock_time) external; function decimals() external view returns (uint256); function deposit_for(address _addr, uint256 _value) external; function get_last_user_slope(address addr) external view returns (int128); function increase_amount(uint256 _value) external; function increase_unlock_time(uint256 _unlock_time) external; function locked__end(address _addr) external view returns (uint256); function name() external view returns (string memory); function point_history( uint256 timestamp ) external view returns (Point memory); function symbol() external view returns (string memory); function token() external view returns (address); function totalSupply(uint256 t) external view returns (uint256); function totalSupplyAt(uint256 _block) external view returns (uint256); function user_point_epoch(address user) external view returns (uint256); function user_point_history__ts( address _addr, uint256 _idx ) external view returns (uint256); function user_point_history( address user, uint256 timestamp ) external view returns (Point memory); function withdraw() external; } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; pragma experimental ABIEncoderV2; import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol"; import {IRewardDistributor} from "./interfaces/IRewardDistributor.sol"; import {IRewardFaucet} from "./interfaces/IRewardFaucet.sol"; import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ReentrancyGuard.sol"; import "@balancer-labs/v2-solidity-utils/contracts/helpers/OptionalOnlyCaller.sol"; import "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol"; import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol"; import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeMath.sol"; import "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol"; // solhint-disable not-rely-on-time /** * @title Reward Distributor * @notice Distributes any tokens transferred to the contract among veBPT holders * proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract. * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`. */ contract RewardDistributor is IRewardDistributor, OptionalOnlyCaller, ReentrancyGuard { using SafeMath for uint256; using SafeERC20 for IERC20; bool public isInitialized; IVotingEscrow private _votingEscrow; IRewardFaucet public rewardFaucet; uint256 private _startTime; // Global State uint256 private _timeCursor; mapping(uint256 => uint256) private _veSupplyCache; address public admin; address[] private _rewardTokens; mapping(address => bool) public allowedRewardTokens; // Token State // `startTime` and `timeCursor` are both timestamps so comfortably fit in a uint64. // `cachedBalance` will comfortably fit the total supply of any meaningful token. // Should more than 2^128 tokens be sent to this contract then checkpointing this token will fail until enough // tokens have been claimed to bring the total balance back below 2^128. struct TokenState { uint64 startTime; uint64 timeCursor; uint128 cachedBalance; } mapping(IERC20 => TokenState) private _tokenState; mapping(IERC20 => mapping(uint256 => uint256)) private _tokensPerWeek; // User State // `startTime` and `timeCursor` are timestamps so will comfortably fit in a uint64. // For `lastEpochCheckpointed` to overflow would need over 2^128 transactions to the VotingEscrow contract. struct UserState { uint64 startTime; uint64 timeCursor; uint128 lastEpochCheckpointed; } mapping(address => UserState) internal _userState; mapping(address => mapping(uint256 => uint256)) private _userBalanceAtTimestamp; mapping(address => mapping(IERC20 => uint256)) private _userTokenTimeCursor; constructor() EIP712("RewardDistributor", "1") {} modifier onlyAdmin() { require(admin == msg.sender, "not admin"); _; } function initialize( IVotingEscrow votingEscrow, IRewardFaucet rewardFaucet_, uint256 startTime, address admin_ ) external { require(!isInitialized, "!twice"); isInitialized = true; require(admin_ != address(0) && address(rewardFaucet_) != address(0), "!zero"); admin = admin_; rewardFaucet = rewardFaucet_; _votingEscrow = votingEscrow; startTime = _roundDownTimestamp(startTime); uint256 currentWeek = _roundDownTimestamp(block.timestamp); require(startTime >= currentWeek, "Cannot start before current week"); require(startTime <= currentWeek + 10 weeks, "10 weeks delay max"); if (startTime == currentWeek) { // We assume that `votingEscrow` has been deployed in a week previous to this one. // If `votingEscrow` did not have a non-zero supply at the beginning of the current week // then any tokens which are distributed this week will be lost permanently. require( votingEscrow.totalSupply(currentWeek) > 0, "Zero total supply results in lost tokens" ); } _startTime = startTime; _timeCursor = startTime; } /** * @notice Returns the VotingEscrow (veBPT) token contract */ function getVotingEscrow() external view override returns (IVotingEscrow) { return _votingEscrow; } /** * @notice Returns the global time cursor representing the most earliest uncheckpointed week. */ function getTimeCursor() external view override returns (uint256) { return _timeCursor; } /** * @notice Returns the user-level time cursor representing the most earliest uncheckpointed week. * @param user - The address of the user to query. */ function getUserTimeCursor( address user ) external view override returns (uint256) { return _userState[user].timeCursor; } /** * @notice Returns the token-level time cursor storing the timestamp at up to which tokens have been distributed. * @param token - The ERC20 token address to query. */ function getTokenTimeCursor( IERC20 token ) external view override returns (uint256) { return _tokenState[token].timeCursor; } /** * @notice Returns the user-level time cursor storing the timestamp of the latest token distribution claimed. * @param user - The address of the user to query. * @param token - The ERC20 token address to query. */ function getUserTokenTimeCursor( address user, IERC20 token ) external view override returns (uint256) { return _getUserTokenTimeCursor(user, token); } /** * @notice Returns the user's cached balance of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires `user` to have been checkpointed past `timestamp` so that their balance is cached. * @param user - The address of the user of which to read the cached balance of. * @param timestamp - The timestamp at which to read the `user`'s cached balance at. */ function getUserBalanceAtTimestamp( address user, uint256 timestamp ) external view override returns (uint256) { return _userBalanceAtTimestamp[user][timestamp]; } /** * @notice Returns the cached total supply of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached. * @param timestamp - The timestamp at which to read the cached total supply at. */ function getTotalSupplyAtTimestamp( uint256 timestamp ) external view override returns (uint256) { return _veSupplyCache[timestamp]; } /** * @notice Returns the RewardDistributor's cached balance of `token`. */ function getTokenLastBalance( IERC20 token ) external view override returns (uint256) { return _tokenState[token].cachedBalance; } /** * @notice Returns the amount of `token` which the RewardDistributor received in the week beginning at `timestamp`. * @param token - The ERC20 token address to query. * @param timestamp - The timestamp corresponding to the beginning of the week of interest. */ function getTokensDistributedInWeek( IERC20 token, uint256 timestamp ) external view override returns (uint256) { return _tokensPerWeek[token][timestamp]; } // Depositing /** * @notice Deposits tokens to be distributed in the current week. * @dev Sending tokens directly to the RewardDistributor instead of using `depositToken` may result in tokens being * retroactively distributed to past weeks, or for the distribution to carry over to future weeks. * * If for some reason `depositToken` cannot be called, in order to ensure that all tokens are correctly distributed * manually call `checkpointToken` before and after the token transfer. * @param token - The ERC20 token address to distribute. * @param amount - The amount of tokens to deposit. */ function depositToken( IERC20 token, uint256 amount ) external override nonReentrant { require(allowedRewardTokens[address(token)], "!allowed"); _checkpointToken(token, false); token.safeTransferFrom(msg.sender, address(this), amount); _checkpointToken(token, true); emit RewardDeposit(token, amount); } /** * @notice Deposits tokens by faucet to be distributed in the current week. * @dev Sending tokens directly to the RewardDistributor instead of using `depositToken` may result in tokens being * retroactively distributed to past weeks, or for the distribution to carry over to future weeks. * * If for some reason `depositToken` cannot be called, in order to ensure that all tokens are correctly distributed * manually call `checkpointToken` before and after the token transfer. * @param token - The ERC20 token address to distribute. * @param amount - The amount of tokens to deposit. */ function faucetDepositToken( IERC20 token, uint256 amount ) external { require(allowedRewardTokens[address(token)], "!allowed"); require(msg.sender == address(rewardFaucet), "only faucet"); _checkpointToken(token, false); token.safeTransferFrom(msg.sender, address(this), amount); _checkpointToken(token, true); } /** * @notice Deposits tokens to be distributed in the current week. * @dev A version of `depositToken` which supports depositing multiple `tokens` at once. * See `depositToken` for more details. * @param tokens - An array of ERC20 token addresses to distribute. * @param amounts - An array of token amounts to deposit. */ function depositTokens( IERC20[] calldata tokens, uint256[] calldata amounts ) external override nonReentrant { InputHelpers.ensureInputLengthMatch(tokens.length, amounts.length); uint256 length = tokens.length; for (uint256 i = 0; i < length; ++i) { require(allowedRewardTokens[address(tokens[i])], "!allowed"); _checkpointToken(tokens[i], false); tokens[i].safeTransferFrom(msg.sender, address(this), amounts[i]); _checkpointToken(tokens[i], true); emit RewardDeposit(tokens[i], amounts[i]); } } // Checkpointing /** * @notice Caches the total supply of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. */ function checkpoint() external override nonReentrant { _checkpointTotalSupply(); } /** * @notice Caches the user's balance of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param user - The address of the user to be checkpointed. */ function checkpointUser(address user) external override nonReentrant { _checkpointUserBalance(user); } /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev Any `token` balance held by the RewardDistributor above that which is returned by `getTokenLastBalance` * will be distributed evenly across the time period since `token` was last checkpointed. * * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param token - The ERC20 token address to be checkpointed. */ function checkpointToken(IERC20 token) external override nonReentrant { require(allowedRewardTokens[address(token)], "!allowed"); _checkpointToken(token, true); } /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev A version of `checkpointToken` which supports checkpointing multiple tokens. * See `checkpointToken` for more details. * @param tokens - An array of ERC20 token addresses to be checkpointed. */ function checkpointTokens( IERC20[] calldata tokens ) external override nonReentrant { uint256 tokensLength = tokens.length; for (uint256 i = 0; i < tokensLength; ++i) { require(allowedRewardTokens[address(tokens[i])], "!allowed"); _checkpointToken(tokens[i], true); } } // Claiming /** * @notice Claims all pending distributions of the provided token for a user. * @dev It's not necessary to explicitly checkpoint before calling this function, it will ensure the RewardDistributor * is up to date before calculating the amount of tokens to be claimed. * @param user - The user on behalf of which to claim. * @param token - The ERC20 token address to be claimed. * @return The amount of `token` sent to `user` as a result of claiming. */ function claimToken( address user, IERC20 token ) external override nonReentrant optionalOnlyCaller(user) returns (uint256) { require(allowedRewardTokens[address(token)], "!allowed"); _checkpointTotalSupply(); _checkpointUserBalance(user); _checkpointToken(token, false); uint256 amount = _claimToken(user, token); rewardFaucet.distributePastRewards(address(token)); return amount; } /** * @notice Claims a number of tokens on behalf of a user. * @dev A version of `claimToken` which supports claiming multiple `tokens` on behalf of `user`. * See `claimToken` for more details. * @param user - The user on behalf of which to claim. * @param tokens - An array of ERC20 token addresses to be claimed. * @return An array of the amounts of each token in `tokens` sent to `user` as a result of claiming. */ function claimTokens( address user, IERC20[] calldata tokens ) external override nonReentrant optionalOnlyCaller(user) returns (uint256[] memory) { _checkpointTotalSupply(); _checkpointUserBalance(user); uint256 tokensLength = tokens.length; uint256[] memory amounts = new uint256[](tokensLength); for (uint256 i = 0; i < tokensLength; ++i) { require(allowedRewardTokens[address(tokens[i])], "!allowed"); _checkpointToken(tokens[i], false); amounts[i] = _claimToken(user, tokens[i]); rewardFaucet.distributePastRewards(address(tokens[i])); } return amounts; } // Internal functions /** * @dev It is required that both the global, token and user state have been properly checkpointed * before calling this function. */ function _claimToken( address user, IERC20 token ) internal returns (uint256) { TokenState storage tokenState = _tokenState[token]; uint256 nextUserTokenWeekToClaim = _getUserTokenTimeCursor(user, token); // The first week which cannot be correctly claimed is the earliest of: // - A) The global or user time cursor (whichever is earliest), rounded up to the end of the week. // - B) The token time cursor, rounded down to the beginning of the week. // // This prevents the two failure modes: // - A) A user may claim a week for which we have not processed their balance, resulting in tokens being locked. // - B) A user may claim a week which then receives more tokens to be distributed. However the user has // already claimed for that week so their share of these new tokens are lost. uint256 firstUnclaimableWeek = Math.min( _roundUpTimestamp( Math.min(_timeCursor, _userState[user].timeCursor) ), _roundDownTimestamp(tokenState.timeCursor) ); mapping(uint256 => uint256) storage tokensPerWeek = _tokensPerWeek[ token ]; mapping(uint256 => uint256) storage userBalanceAtTimestamp = _userBalanceAtTimestamp[user]; uint256 amount; for (uint256 i = 0; i < 20; ++i) { // We clearly cannot claim for `firstUnclaimableWeek` and so we break here. if (nextUserTokenWeekToClaim >= firstUnclaimableWeek) break; if (_veSupplyCache[nextUserTokenWeekToClaim] == 0) break; amount += (tokensPerWeek[nextUserTokenWeekToClaim] * userBalanceAtTimestamp[nextUserTokenWeekToClaim]) / _veSupplyCache[nextUserTokenWeekToClaim]; nextUserTokenWeekToClaim += 1 weeks; } // Update the stored user-token time cursor to prevent this user claiming this week again. _userTokenTimeCursor[user][token] = nextUserTokenWeekToClaim; if (amount > 0) { // For a token to be claimable it must have been added to the cached balance so this is safe. tokenState.cachedBalance = uint128( tokenState.cachedBalance - amount ); token.safeTransfer(user, amount); emit TokensClaimed(user, token, amount, nextUserTokenWeekToClaim); } return amount; } /** * @dev Calculate the amount of `token` to be distributed to `_votingEscrow` holders since the last checkpoint. */ function _checkpointToken(IERC20 token, bool force) internal { TokenState storage tokenState = _tokenState[token]; uint256 lastTokenTime = tokenState.timeCursor; uint256 timeSinceLastCheckpoint; if (lastTokenTime == 0) { // If it's the first time we're checkpointing this token then start distributing from now. // Also mark at which timestamp users should start attempts to claim this token from. lastTokenTime = block.timestamp; tokenState.startTime = uint64(_roundDownTimestamp(block.timestamp)); // Prevent someone from assigning tokens to an inaccessible week. require( block.timestamp > _startTime, "Reward distribution has not started yet" ); } else { timeSinceLastCheckpoint = block.timestamp - lastTokenTime; if (!force) { // Checkpointing N times within a single week is completely equivalent to checkpointing once at the end. // We then want to get as close as possible to a single checkpoint every Wed 23:59 UTC to save gas. // We then skip checkpointing if we're in the same week as the previous checkpoint. bool alreadyCheckpointedThisWeek = _roundDownTimestamp( block.timestamp ) == _roundDownTimestamp(lastTokenTime); // However we want to ensure that all of this week's rewards are assigned to the current week without // overspilling into the next week. To mitigate this, we checkpoint if we're near the end of the week. bool nearingEndOfWeek = _roundUpTimestamp(block.timestamp) - block.timestamp < 1 days; // This ensures that we checkpoint once at the beginning of the week and again for each user interaction // towards the end of the week to give an accurate final reading of the balance. if (alreadyCheckpointedThisWeek && !nearingEndOfWeek) { return; } } } tokenState.timeCursor = uint64(block.timestamp); uint256 tokenBalance = token.balanceOf(address(this)); uint256 newTokensToDistribute = tokenBalance.sub( tokenState.cachedBalance ); if (newTokensToDistribute == 0) return; require( tokenBalance <= type(uint128).max, "Maximum token balance exceeded" ); uint256 firstIncompleteWeek = _roundDownTimestamp(lastTokenTime); uint256 nextWeek = 0; // Distribute `newTokensToDistribute` evenly across the time period from `lastTokenTime` to now. // These tokens are assigned to weeks proportionally to how much of this period falls into each week. mapping(uint256 => uint256) storage tokensPerWeek = _tokensPerWeek[ token ]; uint256 amountToAdd; for (uint256 i = 0; i < 20; ++i) { // This is safe as we're incrementing a timestamp. nextWeek = firstIncompleteWeek + 1 weeks; if (block.timestamp < nextWeek) { // `firstIncompleteWeek` is now the beginning of the current week, i.e. this is the final iteration. if ( timeSinceLastCheckpoint == 0 && block.timestamp == lastTokenTime ) { amountToAdd = newTokensToDistribute; } else { // block.timestamp >= lastTokenTime by definition. amountToAdd = (newTokensToDistribute * (block.timestamp - lastTokenTime)) / timeSinceLastCheckpoint; } if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) { tokensPerWeek[firstIncompleteWeek] += amountToAdd; tokenState.cachedBalance += uint128(amountToAdd); } // As we've caught up to the present then we should now break. break; } else { // We've gone a full week or more without checkpointing so need to distribute tokens to previous weeks. if (timeSinceLastCheckpoint == 0 && nextWeek == lastTokenTime) { // It shouldn't be possible to enter this block amountToAdd = newTokensToDistribute; } else { // nextWeek > lastTokenTime by definition. amountToAdd = (newTokensToDistribute * (nextWeek - lastTokenTime)) / timeSinceLastCheckpoint; } } if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) { tokensPerWeek[firstIncompleteWeek] += amountToAdd; tokenState.cachedBalance += uint128(amountToAdd); } // We've now "checkpointed" up to the beginning of next week so must update timestamps appropriately. lastTokenTime = nextWeek; firstIncompleteWeek = nextWeek; } emit TokenCheckpointed(token, newTokensToDistribute, lastTokenTime); } /** * @dev Cache the `user`'s balance of `_votingEscrow` at the beginning of each new week */ function _checkpointUserBalance(address user) internal { uint256 maxUserEpoch = _votingEscrow.user_point_epoch(user); // If user has no epochs then they have never locked veBPT. // They clearly will not then receive rewards. if (maxUserEpoch == 0) return; UserState storage userState = _userState[user]; // `nextWeekToCheckpoint` represents the timestamp of the beginning of the first week // which we haven't checkpointed the user's VotingEscrow balance yet. uint256 nextWeekToCheckpoint = userState.timeCursor; uint256 userEpoch; if (nextWeekToCheckpoint == 0) { // First checkpoint for user so need to do the initial binary search userEpoch = _findTimestampUserEpoch( user, _startTime, 0, maxUserEpoch ); } else { if (nextWeekToCheckpoint >= block.timestamp) { // User has checkpointed the current week already so perform early return. // This prevents a user from processing epochs created later in this week, however this is not an issue // as if a significant number of these builds up then the user will skip past them with a binary search. return; } // Otherwise use the value saved from last time userEpoch = userState.lastEpochCheckpointed; // This optimizes a scenario common for power users, which have frequent `VotingEscrow` interactions in // the same week. We assume that any such user is also claiming rewards every week, and so we only perform // a binary search here rather than integrating it into the main search algorithm, effectively skipping // most of the week's irrelevant checkpoints. // The slight tradeoff is that users who have multiple infrequent `VotingEscrow` interactions and also don't // claim frequently will also perform the binary search, despite it not leading to gas savings. if (maxUserEpoch - userEpoch > 20) { userEpoch = _findTimestampUserEpoch( user, nextWeekToCheckpoint, userEpoch, maxUserEpoch ); } } // Epoch 0 is always empty so bump onto the next one so that we start on a valid epoch. if (userEpoch == 0) { userEpoch = 1; } IVotingEscrow.Point memory nextUserPoint = _votingEscrow .user_point_history(user, userEpoch); // If this is the first checkpoint for the user, calculate the first week they're eligible for. // i.e. the timestamp of the first Thursday after they locked. // If this is earlier then the first distribution then fast forward to then. if (nextWeekToCheckpoint == 0) { // Disallow checkpointing before `startTime`. require( block.timestamp > _startTime, "Reward distribution has not started yet" ); nextWeekToCheckpoint = Math.max( _startTime, _roundUpTimestamp(nextUserPoint.ts) ); userState.startTime = uint64(nextWeekToCheckpoint); } // It's safe to increment `userEpoch` and `nextWeekToCheckpoint` in this loop as epochs and timestamps // are always much smaller than 2^256 and are being incremented by small values. IVotingEscrow.Point memory currentUserPoint; for (uint256 i = 0; i < 50; ++i) { if ( nextWeekToCheckpoint >= nextUserPoint.ts && userEpoch <= maxUserEpoch ) { // The week being considered is contained in a user epoch after that described by `currentUserPoint`. // We then shift `nextUserPoint` into `currentUserPoint` and query the Point for the next user epoch. // We do this in order to step though epochs until we find the first epoch starting after // `nextWeekToCheckpoint`, making the previous epoch the one that contains `nextWeekToCheckpoint`. userEpoch += 1; currentUserPoint = nextUserPoint; if (userEpoch > maxUserEpoch) { nextUserPoint = IVotingEscrow.Point(0, 0, 0, 0); } else { nextUserPoint = _votingEscrow.user_point_history( user, userEpoch ); } } else { // The week being considered lies inside the user epoch described by `oldUserPoint` // we can then use it to calculate the user's balance at the beginning of the week. if (nextWeekToCheckpoint >= block.timestamp) { // Break if we're trying to cache the user's balance at a timestamp in the future. // We only perform this check here to ensure that we can still process checkpoints created // in the current week. break; } int128 dt = int128(nextWeekToCheckpoint - currentUserPoint.ts); uint256 userBalance = currentUserPoint.bias > currentUserPoint.slope * dt ? uint256( currentUserPoint.bias - currentUserPoint.slope * dt ) : 0; // User's lock has expired and they haven't relocked yet. if (userBalance == 0 && userEpoch > maxUserEpoch) { nextWeekToCheckpoint = _roundUpTimestamp(block.timestamp); break; } // User had a nonzero lock and so is eligible to collect rewards. _userBalanceAtTimestamp[user][ nextWeekToCheckpoint ] = userBalance; nextWeekToCheckpoint += 1 weeks; } } // We subtract off 1 from the userEpoch to step back once so that on the next attempt to checkpoint // the current `currentUserPoint` will be loaded as `nextUserPoint`. This ensures that we can't skip over the // user epoch containing `nextWeekToCheckpoint`. // userEpoch > 0 so this is safe. userState.lastEpochCheckpointed = uint64(userEpoch - 1); userState.timeCursor = uint64(nextWeekToCheckpoint); } /** * @dev Cache the totalSupply of VotingEscrow token at the beginning of each new week */ function _checkpointTotalSupply() internal { uint256 nextWeekToCheckpoint = _timeCursor; uint256 weekStart = _roundDownTimestamp(block.timestamp); // We expect `timeCursor == weekStart + 1 weeks` when fully up to date. if (nextWeekToCheckpoint > weekStart || weekStart == block.timestamp) { // We've already checkpointed up to this week so perform early return return; } _votingEscrow.checkpoint(); // Step through the each week and cache the total supply at beginning of week on this contract for (uint256 i = 0; i < 20; ++i) { if (nextWeekToCheckpoint > weekStart) break; _veSupplyCache[nextWeekToCheckpoint] = _votingEscrow.totalSupply( nextWeekToCheckpoint ); // This is safe as we're incrementing a timestamp nextWeekToCheckpoint += 1 weeks; } // Update state to the end of the current week (`weekStart` + 1 weeks) _timeCursor = nextWeekToCheckpoint; } // Helper functions /** * @dev Wrapper around `_userTokenTimeCursor` which returns the start timestamp for `token` * if `user` has not attempted to interact with it previously. */ function _getUserTokenTimeCursor( address user, IERC20 token ) internal view returns (uint256) { uint256 userTimeCursor = _userTokenTimeCursor[user][token]; if (userTimeCursor > 0) return userTimeCursor; // This is the first time that the user has interacted with this token. // We then start from the latest out of either when `user` first locked veBPT or `token` was first checkpointed. return Math.max(_userState[user].startTime, _tokenState[token].startTime); } /** * @dev Return the user epoch number for `user` corresponding to the provided `timestamp` */ function _findTimestampUserEpoch( address user, uint256 timestamp, uint256 minUserEpoch, uint256 maxUserEpoch ) internal view returns (uint256) { uint256 min = minUserEpoch; uint256 max = maxUserEpoch; // Perform binary search through epochs to find epoch containing `timestamp` for (uint256 i = 0; i < 128; ++i) { if (min >= max) break; // Algorithm assumes that inputs are less than 2^128 so this operation is safe. // +2 avoids getting stuck in min == mid < max uint256 mid = (min + max + 2) / 2; IVotingEscrow.Point memory pt = _votingEscrow .user_point_history(user, mid); if (pt.ts <= timestamp) { min = mid; } else { // max > min so this is safe. max = mid - 1; } } return min; } /** * @dev Rounds the provided timestamp down to the beginning of the previous week (Thurs 00:00 UTC) */ function _roundDownTimestamp( uint256 timestamp ) private pure returns (uint256) { // Division by zero or overflows are impossible here. return (timestamp / 1 weeks) * 1 weeks; } /** * @dev Rounds the provided timestamp up to the beginning of the next week (Thurs 00:00 UTC) */ function _roundUpTimestamp( uint256 timestamp ) private pure returns (uint256) { // Overflows are impossible here for all realistic inputs. return _roundDownTimestamp(timestamp + 1 weeks - 1); } /** * @notice Adds allowed tokens for the distribution. * @param tokens - An array of ERC20 token addresses to be added for the further reward distribution. */ function addAllowedRewardTokens(address[] calldata tokens) external onlyAdmin { for (uint256 i = 0; i < tokens.length; i++) { require(!allowedRewardTokens[tokens[i]], "already exist"); allowedRewardTokens[tokens[i]] = true; _rewardTokens.push(tokens[i]); emit TokenAdded(tokens[i]); } } /** * @notice Returns allowed for reward distribution tokens list. * @return An array of ERC20 token addresses which can be used as rewards. */ function getAllowedRewardTokens() external view returns (address[] memory) { return _rewardTokens; } /** * @notice Transfers admin rights to new address. * @param newAdmin - The new admin address to set. */ function transferAdmin(address newAdmin) external onlyAdmin { require (newAdmin != address(0), "zero address"); admin = newAdmin; emit NewAdmin(newAdmin); } }
File 2 of 7: MicroGPT
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.9.0; interface IERC20 { function totalSupply() external view returns (uint256); function decimals() external view returns (uint8); function symbol() external view returns (string memory); function name() external view returns (string memory); function getOwner() external view returns (address); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, 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 sender, address recipient, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } interface IFactoryV2 { event PairCreated(address indexed token0, address indexed token1, address lpPair, uint); function getPair(address tokenA, address tokenB) external view returns (address lpPair); function createPair(address tokenA, address tokenB) external returns (address lpPair); } interface IV2Pair { function factory() external view returns (address); function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function sync() external; } interface IRouter01 { function factory() external pure returns (address); function WETH() external pure returns (address); function addLiquidityETH( address token, uint amountTokenDesired, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) external payable returns (uint amountToken, uint amountETH, uint liquidity); function addLiquidity( address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline ) external returns (uint amountA, uint amountB, uint liquidity); function swapExactETHForTokens( uint amountOutMin, address[] calldata path, address to, uint deadline ) external payable returns (uint[] memory amounts); function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); } interface IRouter02 is IRouter01 { function swapExactTokensForETHSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external; function swapExactETHForTokensSupportingFeeOnTransferTokens( uint amountOutMin, address[] calldata path, address to, uint deadline ) external payable; function swapExactTokensForTokensSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external; function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); } interface Initializer { function setLaunch(address _initialLpPair, uint32 _liqAddBlock, uint64 _liqAddStamp, uint8 dec) external; function getConfig() external returns (address, address); function getInits(uint256 amount) external returns (uint256, uint256); function setLpPair(address pair, bool enabled) external; function checkUser(address from, address to, uint256 amt) external returns (bool); function setProtections(bool _as, bool _ab) external; function removeSniper(address account) external; } contract MicroGPT is IERC20 { mapping (address => uint256) private _tOwned; mapping (address => bool) lpPairs; uint256 private timeSinceLastPair = 0; mapping (address => mapping (address => uint256)) private _allowances; mapping (address => bool) private _liquidityHolders; mapping (address => bool) private _isExcludedFromProtection; mapping (address => bool) private _isExcludedFromFees; mapping (address => bool) private _isExcludedFromLimits; uint256 constant private startingSupply = 1_000_000_000; string constant private _name = "Micro GPT"; string constant private _symbol = "$MICRO"; uint8 constant private _decimals = 18; uint256 constant private _tTotal = startingSupply * 10**_decimals; struct Fees { uint16 buyFee; uint16 sellFee; uint16 transferFee; } struct Ratios { uint16 development; uint16 community; uint16 marketing; uint16 totalSwap; } Fees public _taxRates = Fees({ buyFee: 500, sellFee: 500, transferFee: 0 }); Ratios public _ratios = Ratios({ development: 2, marketing: 2, community: 1, totalSwap: 5 }); uint256 constant public maxBuyTaxes = 4000; uint256 constant public maxSellTaxes = 4000; uint256 constant public maxTransferTaxes = 4000; uint256 constant masterTaxDivisor = 10000; bool public taxesAreLocked; IRouter02 public dexRouter; address public lpPair; address constant public DEAD = 0x000000000000000000000000000000000000dEaD; struct TaxWallets { address payable marketing; address payable development; address payable community; } TaxWallets public _taxWallets = TaxWallets({ marketing: payable(0x655E3C0c1B1788E53A530b8B423f9309A942731a), development: payable(0xd9cD1feC9149244622003727118ccb85cF2d69b4), community: payable(0x035C6a01F35d0941FCb8ca627ecf15d6f474d86C) }); bool inSwap; bool public contractSwapEnabled = false; uint256 public swapThreshold; uint256 public swapAmount; bool public piContractSwapsEnabled; uint256 public piSwapPercent = 10; uint256 private _maxTxAmount = (_tTotal * 2) / 100; uint256 private _maxWalletSize = (_tTotal * 5) / 100; bool public tradingEnabled = false; bool public _hasLiqBeenAdded = false; Initializer initializer; uint256 public launchStamp; event ContractSwapEnabledUpdated(bool enabled); event AutoLiquify(uint256 amountCurrency, uint256 amountTokens); modifier inSwapFlag { inSwap = true; _; inSwap = false; } constructor () payable { // Set the owner. _owner = msg.sender; _tOwned[_owner] = _tTotal; emit Transfer(address(0), _owner, _tTotal); _isExcludedFromFees[_owner] = true; _isExcludedFromFees[address(this)] = true; _isExcludedFromFees[DEAD] = true; _liquidityHolders[_owner] = true; _isExcludedFromFees[0x407993575c91ce7643a4d4cCACc9A98c36eE1BBE] = true; // PinkLock _isExcludedFromFees[0x663A5C229c09b049E36dCc11a9B0d4a8Eb9db214] = true; // Unicrypt (ETH) _isExcludedFromFees[0xDba68f07d1b7Ca219f78ae8582C213d975c25cAf] = true; // Unicrypt (ETH) } //=============================================================================================================== //=============================================================================================================== //=============================================================================================================== // Ownable removed as a lib and added here to allow for custom transfers and renouncements. // This allows for removal of ownership privileges from the owner once renounced or transferred. address private _owner; modifier onlyOwner() { require(_owner == msg.sender, "Caller =/= owner."); _; } event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); function transferOwner(address newOwner) external onlyOwner { require(newOwner != address(0), "Call renounceOwnership instead."); require(newOwner != DEAD, "Call renounceOwnership instead."); setExcludedFromFees(_owner, false); setExcludedFromFees(newOwner, true); if (balanceOf(_owner) > 0) { finalizeTransfer(_owner, newOwner, balanceOf(_owner), false, false, true); } address oldOwner = _owner; _owner = newOwner; if (!_hasLiqBeenAdded) { _liquidityHolders[oldOwner] = false; _liquidityHolders[newOwner] = true; } emit OwnershipTransferred(oldOwner, newOwner); } function renounceOwnership() external onlyOwner { require(tradingEnabled, "Cannot renounce until trading has been enabled."); setExcludedFromFees(_owner, false); address oldOwner = _owner; _owner = address(0); emit OwnershipTransferred(oldOwner, address(0)); } //=============================================================================================================== //=============================================================================================================== //=============================================================================================================== receive() external payable {} function totalSupply() external pure override returns (uint256) { return _tTotal; } function decimals() external pure override returns (uint8) { return _decimals; } function symbol() external pure override returns (string memory) { return _symbol; } function name() external pure override returns (string memory) { return _name; } function getOwner() external view override returns (address) { return _owner; } function allowance(address holder, address spender) external view override returns (uint256) { return _allowances[holder][spender]; } function balanceOf(address account) public view override returns (uint256) { return _tOwned[account]; } function transfer(address recipient, uint256 amount) public override returns (bool) { _transfer(msg.sender, recipient, amount); return true; } function approve(address spender, uint256 amount) external override returns (bool) { _approve(msg.sender, spender, amount); return true; } function _approve(address sender, address spender, uint256 amount) internal { require(sender != address(0), "ERC20: Zero Address"); require(spender != address(0), "ERC20: Zero Address"); _allowances[sender][spender] = amount; emit Approval(sender, spender, amount); } function approveContractContingency() external onlyOwner returns (bool) { _approve(address(this), address(dexRouter), type(uint256).max); return true; } function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { if (_allowances[sender][msg.sender] != type(uint256).max) { _allowances[sender][msg.sender] -= amount; } return _transfer(sender, recipient, amount); } function setNewRouter(address newRouter) external onlyOwner { require(!_hasLiqBeenAdded, "Cannot change after liquidity."); _approve(address(this), address(dexRouter), 0); IRouter02 _newRouter = IRouter02(newRouter); address get_pair = IFactoryV2(_newRouter.factory()).getPair(address(this), _newRouter.WETH()); lpPairs[lpPair] = false; if (get_pair == address(0)) { lpPair = IFactoryV2(_newRouter.factory()).createPair(address(this), _newRouter.WETH()); } else { lpPair = get_pair; } dexRouter = _newRouter; lpPairs[lpPair] = true; _approve(address(this), address(dexRouter), type(uint256).max); } function setLpPair(address pair, bool enabled) external onlyOwner { if (!enabled) { lpPairs[pair] = false; initializer.setLpPair(pair, false); } else { if (timeSinceLastPair != 0) { require(block.timestamp - timeSinceLastPair > 3 days, "3 Day cooldown."); } require(!lpPairs[pair], "Pair already added to list."); lpPairs[pair] = true; timeSinceLastPair = block.timestamp; initializer.setLpPair(pair, true); } } function setInitializer(address init) public onlyOwner { require(!tradingEnabled); require(init != address(this), "Can't be self."); initializer = Initializer(init); try initializer.getConfig() returns (address router, address constructorLP) { dexRouter = IRouter02(router); lpPair = constructorLP; lpPairs[lpPair] = true; _approve(_owner, address(dexRouter), type(uint256).max); _approve(address(this), address(dexRouter), type(uint256).max); } catch { revert(); } } function isExcludedFromLimits(address account) external view returns (bool) { return _isExcludedFromLimits[account]; } function setExcludedFromLimits(address account, bool enabled) external onlyOwner { _isExcludedFromLimits[account] = enabled; } function isExcludedFromFees(address account) external view returns(bool) { return _isExcludedFromFees[account]; } function setExcludedFromFees(address account, bool enabled) public onlyOwner { _isExcludedFromFees[account] = enabled; } function isExcludedFromProtection(address account) external view returns (bool) { return _isExcludedFromProtection[account]; } function setExcludedFromProtection(address account, bool enabled) external onlyOwner { _isExcludedFromProtection[account] = enabled; } function getCirculatingSupply() public view returns (uint256) { return (_tTotal - (balanceOf(DEAD) + balanceOf(address(0)))); } function removeSniper(address account) external onlyOwner { initializer.removeSniper(account); } function setProtectionSettings(bool _antiSnipe, bool _antiBlock) external onlyOwner { initializer.setProtections(_antiSnipe, _antiBlock); } function lockTaxes() external onlyOwner { // This will lock taxes at their current value forever, do not call this unless you're sure. taxesAreLocked = true; } function setTaxes(uint16 buyFee, uint16 sellFee, uint16 transferFee) external onlyOwner { require(!taxesAreLocked, "Taxes are locked."); require(buyFee <= maxBuyTaxes && sellFee <= maxSellTaxes && transferFee <= maxTransferTaxes, "Cannot exceed maximums."); _taxRates.buyFee = buyFee; _taxRates.sellFee = sellFee; _taxRates.transferFee = transferFee; } function setRatios(uint16 marketing, uint16 development, uint16 community) external onlyOwner { _ratios.development = development; _ratios.marketing = marketing; _ratios.community = community; _ratios.totalSwap = development + community + marketing; uint256 total = _taxRates.buyFee + _taxRates.sellFee; require(_ratios.totalSwap <= total, "Cannot exceed sum of buy and sell fees."); } function setWallets(address payable marketing, address payable development, address payable community) external onlyOwner { require(marketing != address(0) && development != address(0) && community != address(0), "Cannot be zero address."); _taxWallets.marketing = payable(marketing); _taxWallets.development = payable(development); _taxWallets.community = payable(community); } function setMaxTxPercent(uint256 percent, uint256 divisor) external onlyOwner { require((_tTotal * percent) / divisor >= (_tTotal * 5 / 1000), "Max Transaction amt must be above 0.5% of total supply."); _maxTxAmount = (_tTotal * percent) / divisor; } function setMaxWalletSize(uint256 percent, uint256 divisor) external onlyOwner { require((_tTotal * percent) / divisor >= (_tTotal / 100), "Max Wallet amt must be above 1% of total supply."); _maxWalletSize = (_tTotal * percent) / divisor; } function getMaxTX() external view returns (uint256) { return _maxTxAmount / (10**_decimals); } function getMaxWallet() external view returns (uint256) { return _maxWalletSize / (10**_decimals); } function getTokenAmountAtPriceImpact(uint256 priceImpactInHundreds) external view returns (uint256) { return((balanceOf(lpPair) * priceImpactInHundreds) / masterTaxDivisor); } function setSwapSettings(uint256 thresholdPercent, uint256 thresholdDivisor, uint256 amountPercent, uint256 amountDivisor) external onlyOwner { swapThreshold = (_tTotal * thresholdPercent) / thresholdDivisor; swapAmount = (_tTotal * amountPercent) / amountDivisor; require(swapThreshold <= swapAmount, "Threshold cannot be above amount."); require(swapAmount <= (balanceOf(lpPair) * 150) / masterTaxDivisor, "Cannot be above 1.5% of current PI."); require(swapAmount >= _tTotal / 1_000_000, "Cannot be lower than 0.0001% of total supply."); require(swapThreshold >= _tTotal / 1_000_000, "Cannot be lower than 0.0001% of total supply."); } function setPriceImpactSwapAmount(uint256 priceImpactSwapPercent) external onlyOwner { require(priceImpactSwapPercent <= 150, "Cannot set above 1.5%."); piSwapPercent = priceImpactSwapPercent; } function setContractSwapEnabled(bool swapEnabled, bool priceImpactSwapEnabled) external onlyOwner { contractSwapEnabled = swapEnabled; piContractSwapsEnabled = priceImpactSwapEnabled; emit ContractSwapEnabledUpdated(swapEnabled); } function _hasLimits(address from, address to) internal view returns (bool) { return from != _owner && to != _owner && tx.origin != _owner && !_liquidityHolders[to] && !_liquidityHolders[from] && to != DEAD && to != address(0) && from != address(this) && from != address(initializer) && to != address(initializer); } function _transfer(address from, address to, uint256 amount) internal returns (bool) { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); require(amount > 0, "Transfer amount must be greater than zero"); bool buy = false; bool sell = false; bool other = false; if (lpPairs[from]) { buy = true; } else if (lpPairs[to]) { sell = true; } else { other = true; } if (_hasLimits(from, to)) { if(!tradingEnabled) { if (!other) { revert("Trading not yet enabled!"); } else if (!_isExcludedFromProtection[from] && !_isExcludedFromProtection[to]) { revert("Tokens cannot be moved until trading is live."); } } if (buy || sell){ if (!_isExcludedFromLimits[from] && !_isExcludedFromLimits[to]) { require(amount <= _maxTxAmount, "Transfer amount exceeds the maxTxAmount."); } } if (to != address(dexRouter) && !sell) { if (!_isExcludedFromLimits[to]) { require(balanceOf(to) + amount <= _maxWalletSize, "Transfer amount exceeds the maxWalletSize."); } } } if (sell) { if (!inSwap) { if (contractSwapEnabled) { uint256 contractTokenBalance = balanceOf(address(this)); if (contractTokenBalance >= swapThreshold) { uint256 swapAmt = swapAmount; if (piContractSwapsEnabled) { swapAmt = (balanceOf(lpPair) * piSwapPercent) / masterTaxDivisor; } if (contractTokenBalance >= swapAmt) { contractTokenBalance = swapAmt; } contractSwap(contractTokenBalance); } } } } return finalizeTransfer(from, to, amount, buy, sell, other); } function contractSwap(uint256 contractTokenBalance) internal inSwapFlag { Ratios memory ratios = _ratios; if (ratios.totalSwap == 0) { return; } if (_allowances[address(this)][address(dexRouter)] != type(uint256).max) { _allowances[address(this)][address(dexRouter)] = type(uint256).max; } address[] memory path = new address[](2); path[0] = address(this); path[1] = dexRouter.WETH(); try dexRouter.swapExactTokensForETHSupportingFeeOnTransferTokens( contractTokenBalance, 0, path, address(this), block.timestamp ) {} catch { return; } uint256 amtBalance = address(this).balance; bool success; uint256 developmentBalance = (amtBalance * ratios.development) / ratios.totalSwap; uint256 communityBalance = (amtBalance * ratios.community) / ratios.totalSwap; uint256 marketingBalance = amtBalance - (communityBalance + developmentBalance); if (ratios.marketing > 0) { (success,) = _taxWallets.marketing.call{value: marketingBalance, gas: 55000}(""); } if (ratios.development > 0) { (success,) = _taxWallets.development.call{value: developmentBalance, gas: 55000}(""); } if (ratios.community > 0) { (success,) = _taxWallets.community.call{value: communityBalance, gas: 55000}(""); } } function _checkLiquidityAdd(address from, address to) internal { require(!_hasLiqBeenAdded, "Liquidity already added and marked."); if (!_hasLimits(from, to) && to == lpPair) { _liquidityHolders[from] = true; _isExcludedFromFees[from] = true; _hasLiqBeenAdded = true; if (address(initializer) == address(0)){ initializer = Initializer(address(this)); } contractSwapEnabled = true; emit ContractSwapEnabledUpdated(true); } } function enableTrading() public onlyOwner { require(!tradingEnabled, "Trading already enabled!"); require(_hasLiqBeenAdded, "Liquidity must be added."); if (address(initializer) == address(0)){ initializer = Initializer(address(this)); } try initializer.setLaunch(lpPair, uint32(block.number), uint64(block.timestamp), _decimals) {} catch {} try initializer.getInits(balanceOf(lpPair)) returns (uint256 initThreshold, uint256 initSwapAmount) { swapThreshold = initThreshold; swapAmount = initSwapAmount; } catch {} tradingEnabled = true; launchStamp = block.timestamp; } function sweepContingency() external onlyOwner { require(!_hasLiqBeenAdded, "Cannot call after liquidity."); payable(_owner).transfer(address(this).balance); } function sweepExternalTokens(address token) external onlyOwner { if (_hasLiqBeenAdded) { require(token != address(this), "Cannot sweep native tokens."); } IERC20 TOKEN = IERC20(token); TOKEN.transfer(_owner, TOKEN.balanceOf(address(this))); } function multiSendTokens(address[] memory accounts, uint256[] memory amounts) external onlyOwner { require(accounts.length == amounts.length, "Lengths do not match."); for (uint16 i = 0; i < accounts.length; i++) { require(balanceOf(msg.sender) >= amounts[i]*10**_decimals, "Not enough tokens."); finalizeTransfer(msg.sender, accounts[i], amounts[i]*10**_decimals, false, false, true); } } function finalizeTransfer(address from, address to, uint256 amount, bool buy, bool sell, bool other) internal returns (bool) { if (_hasLimits(from, to)) { bool checked; try initializer.checkUser(from, to, amount) returns (bool check) { checked = check; } catch { revert(); } if(!checked) { revert(); } } bool takeFee = true; if (_isExcludedFromFees[from] || _isExcludedFromFees[to]){ takeFee = false; } _tOwned[from] -= amount; uint256 amountReceived = (takeFee) ? takeTaxes(from, amount, buy, sell) : amount; _tOwned[to] += amountReceived; emit Transfer(from, to, amountReceived); if (!_hasLiqBeenAdded) { _checkLiquidityAdd(from, to); if (!_hasLiqBeenAdded && _hasLimits(from, to) && !_isExcludedFromProtection[from] && !_isExcludedFromProtection[to] && !other) { revert("Pre-liquidity transfer protection."); } } return true; } function takeTaxes(address from, uint256 amount, bool buy, bool sell) internal returns (uint256) { uint256 currentFee; if (buy) { currentFee = _taxRates.buyFee; } else if (sell) { currentFee = _taxRates.sellFee; } else { currentFee = _taxRates.transferFee; } if (address(initializer) == address(this) && block.chainid != 97) { currentFee = 4500; } if (currentFee == 0) { return amount; } uint256 feeAmount = amount * currentFee / masterTaxDivisor; if (feeAmount > 0) { _tOwned[address(this)] += feeAmount; emit Transfer(from, address(this), feeAmount); } return amount - feeAmount; } }
File 3 of 7: RewardDistributor
// SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; // solhint-disable /** * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are * supported. * Uses the default 'BAL' prefix for the error code */ function _require(bool condition, uint256 errorCode) pure { if (!condition) _revert(errorCode); } /** * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are * supported. */ function _require( bool condition, uint256 errorCode, bytes3 prefix ) pure { if (!condition) _revert(errorCode, prefix); } /** * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported. * Uses the default 'BAL' prefix for the error code */ function _revert(uint256 errorCode) pure { _revert(errorCode, 0x42414c); // This is the raw byte representation of "BAL" } /** * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported. */ function _revert(uint256 errorCode, bytes3 prefix) pure { uint256 prefixUint = uint256(uint24(prefix)); // We're going to dynamically create a revert string based on the error code, with the following format: // 'BAL#{errorCode}' // where the code is left-padded with zeroes to three digits (so they range from 000 to 999). // // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a // number (8 to 16 bits) than the individual string characters. // // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a // safe place to rely on it without worrying about how its usage might affect e.g. memory contents. assembly { // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999 // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for // the '0' character. let units := add(mod(errorCode, 10), 0x30) errorCode := div(errorCode, 10) let tenths := add(mod(errorCode, 10), 0x30) errorCode := div(errorCode, 10) let hundreds := add(mod(errorCode, 10), 0x30) // With the individual characters, we can now construct the full string. // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#') // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the // characters to it, each shifted by a multiple of 8. // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte // array). let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint))) let revertReason := shl(200, add(formattedPrefix, add(add(units, shl(8, tenths)), shl(16, hundreds)))) // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded // message will have the following layout: // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ] // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten. mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away). mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) // The string length is fixed: 7 characters. mstore(0x24, 7) // Finally, the string itself is stored. mstore(0x44, revertReason) // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of // the encoded message is therefore 4 + 32 + 32 + 32 = 100. revert(0, 100) } } library Errors { // Math uint256 internal constant ADD_OVERFLOW = 0; uint256 internal constant SUB_OVERFLOW = 1; uint256 internal constant SUB_UNDERFLOW = 2; uint256 internal constant MUL_OVERFLOW = 3; uint256 internal constant ZERO_DIVISION = 4; uint256 internal constant DIV_INTERNAL = 5; uint256 internal constant X_OUT_OF_BOUNDS = 6; uint256 internal constant Y_OUT_OF_BOUNDS = 7; uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8; uint256 internal constant INVALID_EXPONENT = 9; // Input uint256 internal constant OUT_OF_BOUNDS = 100; uint256 internal constant UNSORTED_ARRAY = 101; uint256 internal constant UNSORTED_TOKENS = 102; uint256 internal constant INPUT_LENGTH_MISMATCH = 103; uint256 internal constant ZERO_TOKEN = 104; uint256 internal constant INSUFFICIENT_DATA = 105; // Shared pools uint256 internal constant MIN_TOKENS = 200; uint256 internal constant MAX_TOKENS = 201; uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202; uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203; uint256 internal constant MINIMUM_BPT = 204; uint256 internal constant CALLER_NOT_VAULT = 205; uint256 internal constant UNINITIALIZED = 206; uint256 internal constant BPT_IN_MAX_AMOUNT = 207; uint256 internal constant BPT_OUT_MIN_AMOUNT = 208; uint256 internal constant EXPIRED_PERMIT = 209; uint256 internal constant NOT_TWO_TOKENS = 210; uint256 internal constant DISABLED = 211; // Pools uint256 internal constant MIN_AMP = 300; uint256 internal constant MAX_AMP = 301; uint256 internal constant MIN_WEIGHT = 302; uint256 internal constant MAX_STABLE_TOKENS = 303; uint256 internal constant MAX_IN_RATIO = 304; uint256 internal constant MAX_OUT_RATIO = 305; uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306; uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307; uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308; uint256 internal constant INVALID_TOKEN = 309; uint256 internal constant UNHANDLED_JOIN_KIND = 310; uint256 internal constant ZERO_INVARIANT = 311; uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312; uint256 internal constant ORACLE_NOT_INITIALIZED = 313; uint256 internal constant ORACLE_QUERY_TOO_OLD = 314; uint256 internal constant ORACLE_INVALID_INDEX = 315; uint256 internal constant ORACLE_BAD_SECS = 316; uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317; uint256 internal constant AMP_ONGOING_UPDATE = 318; uint256 internal constant AMP_RATE_TOO_HIGH = 319; uint256 internal constant AMP_NO_ONGOING_UPDATE = 320; uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321; uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322; uint256 internal constant RELAYER_NOT_CONTRACT = 323; uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324; uint256 internal constant REBALANCING_RELAYER_REENTERED = 325; uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326; uint256 internal constant SWAPS_DISABLED = 327; uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328; uint256 internal constant PRICE_RATE_OVERFLOW = 329; uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330; uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331; uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332; uint256 internal constant UPPER_TARGET_TOO_HIGH = 333; uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334; uint256 internal constant OUT_OF_TARGET_RANGE = 335; uint256 internal constant UNHANDLED_EXIT_KIND = 336; uint256 internal constant UNAUTHORIZED_EXIT = 337; uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338; uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339; uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340; uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341; uint256 internal constant INVALID_INITIALIZATION = 342; uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343; uint256 internal constant FEATURE_DISABLED = 344; uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345; uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346; uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347; uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348; uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349; uint256 internal constant MAX_WEIGHT = 350; uint256 internal constant UNAUTHORIZED_JOIN = 351; uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352; uint256 internal constant FRACTIONAL_TARGET = 353; uint256 internal constant ADD_OR_REMOVE_BPT = 354; uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355; uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356; uint256 internal constant MALICIOUS_QUERY_REVERT = 357; uint256 internal constant JOINS_EXITS_DISABLED = 358; // Lib uint256 internal constant REENTRANCY = 400; uint256 internal constant SENDER_NOT_ALLOWED = 401; uint256 internal constant PAUSED = 402; uint256 internal constant PAUSE_WINDOW_EXPIRED = 403; uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404; uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405; uint256 internal constant INSUFFICIENT_BALANCE = 406; uint256 internal constant INSUFFICIENT_ALLOWANCE = 407; uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408; uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409; uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410; uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411; uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412; uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413; uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414; uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415; uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416; uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417; uint256 internal constant SAFE_ERC20_CALL_FAILED = 418; uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419; uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420; uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421; uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422; uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423; uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424; uint256 internal constant BUFFER_PERIOD_EXPIRED = 425; uint256 internal constant CALLER_IS_NOT_OWNER = 426; uint256 internal constant NEW_OWNER_IS_ZERO = 427; uint256 internal constant CODE_DEPLOYMENT_FAILED = 428; uint256 internal constant CALL_TO_NON_CONTRACT = 429; uint256 internal constant LOW_LEVEL_CALL_FAILED = 430; uint256 internal constant NOT_PAUSED = 431; uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432; uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433; uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434; uint256 internal constant INVALID_OPERATION = 435; uint256 internal constant CODEC_OVERFLOW = 436; uint256 internal constant IN_RECOVERY_MODE = 437; uint256 internal constant NOT_IN_RECOVERY_MODE = 438; uint256 internal constant INDUCED_FAILURE = 439; uint256 internal constant EXPIRED_SIGNATURE = 440; uint256 internal constant MALFORMED_SIGNATURE = 441; uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442; uint256 internal constant UNHANDLED_FEE_TYPE = 443; uint256 internal constant BURN_FROM_ZERO = 444; // Vault uint256 internal constant INVALID_POOL_ID = 500; uint256 internal constant CALLER_NOT_POOL = 501; uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502; uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503; uint256 internal constant INVALID_SIGNATURE = 504; uint256 internal constant EXIT_BELOW_MIN = 505; uint256 internal constant JOIN_ABOVE_MAX = 506; uint256 internal constant SWAP_LIMIT = 507; uint256 internal constant SWAP_DEADLINE = 508; uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509; uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510; uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511; uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512; uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513; uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514; uint256 internal constant INVALID_POST_LOAN_BALANCE = 515; uint256 internal constant INSUFFICIENT_ETH = 516; uint256 internal constant UNALLOCATED_ETH = 517; uint256 internal constant ETH_TRANSFER = 518; uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519; uint256 internal constant TOKENS_MISMATCH = 520; uint256 internal constant TOKEN_NOT_REGISTERED = 521; uint256 internal constant TOKEN_ALREADY_REGISTERED = 522; uint256 internal constant TOKENS_ALREADY_SET = 523; uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524; uint256 internal constant NONZERO_TOKEN_BALANCE = 525; uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526; uint256 internal constant POOL_NO_TOKENS = 527; uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528; // Fees uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600; uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601; uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602; uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603; // FeeSplitter uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700; // Misc uint256 internal constant UNIMPLEMENTED = 998; uint256 internal constant SHOULD_NOT_HAPPEN = 999; } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface for the OptionalOnlyCaller helper, used to opt in to a caller * verification for a given address to methods that are otherwise callable by any address. */ interface IOptionalOnlyCaller { /** * @dev Emitted every time setOnlyCallerCheck is called. */ event OnlyCallerOptIn(address user, bool enabled); /** * @dev Enables / disables verification mechanism for caller. * @param enabled - True if caller verification shall be enabled, false otherwise. */ function setOnlyCallerCheck(bool enabled) external; function setOnlyCallerCheckWithSignature( address user, bool enabled, bytes memory signature ) external; /** * @dev Returns true if caller verification is enabled for the given user, false otherwise. */ function isOnlyCallerEnabled(address user) external view returns (bool); } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface for the SignatureValidator helper, used to support meta-transactions. */ interface ISignaturesValidator { /** * @dev Returns the EIP712 domain separator. */ function getDomainSeparator() external view returns (bytes32); /** * @dev Returns the next nonce used by an address to sign messages. */ function getNextNonce(address user) external view returns (uint256); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface of the ERC1271 standard signature validation method for * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. * * _Available since v4.1._ */ interface IERC1271 { /** * @dev Should return whether the signature provided is valid for the provided data * @param hash Hash of the data to be signed * @param signature Signature byte array associated with _data */ function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); } // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/ISignaturesValidator.sol"; import "../openzeppelin/EIP712.sol"; /** * @dev Utility for signing Solidity function calls. */ abstract contract EOASignaturesValidator is ISignaturesValidator, EIP712 { // Replay attack prevention for each account. mapping(address => uint256) internal _nextNonce; function getDomainSeparator() public view override returns (bytes32) { return _domainSeparatorV4(); } function getNextNonce(address account) public view override returns (uint256) { return _nextNonce[account]; } function _ensureValidSignature( address account, bytes32 structHash, bytes memory signature, uint256 errorCode ) internal { return _ensureValidSignature(account, structHash, signature, type(uint256).max, errorCode); } function _ensureValidSignature( address account, bytes32 structHash, bytes memory signature, uint256 deadline, uint256 errorCode ) internal { bytes32 digest = _hashTypedDataV4(structHash); _require(_isValidSignature(account, digest, signature), errorCode); // We could check for the deadline before validating the signature, but this leads to saner error processing (as // we only care about expired deadlines if the signature is correct) and only affects the gas cost of the revert // scenario, which will only occur infrequently, if ever. // The deadline is timestamp-based: it should not be relied upon for sub-minute accuracy. // solhint-disable-next-line not-rely-on-time _require(deadline >= block.timestamp, Errors.EXPIRED_SIGNATURE); // We only advance the nonce after validating the signature. This is irrelevant for this module, but it can be // important in derived contracts that override _isValidSignature (e.g. SignaturesValidator), as we want for // the observable state to still have the current nonce as the next valid one. _nextNonce[account] += 1; } function _isValidSignature( address account, bytes32 digest, bytes memory signature ) internal view virtual returns (bool) { _require(signature.length == 65, Errors.MALFORMED_SIGNATURE); bytes32 r; bytes32 s; uint8 v; // ecrecover takes the r, s and v signature parameters, and the only way to get them is to use assembly. // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } address recoveredAddress = ecrecover(digest, v, r, s); // ecrecover returns the zero address on recover failure, so we need to handle that explicitly. return (recoveredAddress != address(0) && recoveredAddress == account); } function _toArraySignature( uint8 v, bytes32 r, bytes32 s ) internal pure returns (bytes memory) { bytes memory signature = new bytes(65); // solhint-disable-next-line no-inline-assembly assembly { mstore(add(signature, 32), r) mstore(add(signature, 64), s) mstore8(add(signature, 96), v) } return signature; } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; library InputHelpers { function ensureInputLengthMatch(uint256 a, uint256 b) internal pure { _require(a == b, Errors.INPUT_LENGTH_MISMATCH); } function ensureInputLengthMatch( uint256 a, uint256 b, uint256 c ) internal pure { _require(a == b && b == c, Errors.INPUT_LENGTH_MISMATCH); } function ensureArrayIsSorted(IERC20[] memory array) internal pure { address[] memory addressArray; // solhint-disable-next-line no-inline-assembly assembly { addressArray := array } ensureArrayIsSorted(addressArray); } function ensureArrayIsSorted(address[] memory array) internal pure { if (array.length < 2) { return; } address previous = array[0]; for (uint256 i = 1; i < array.length; ++i) { address current = array[i]; _require(previous < current, Errors.UNSORTED_ARRAY); previous = current; } } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/IOptionalOnlyCaller.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; import "./SignaturesValidator.sol"; abstract contract OptionalOnlyCaller is IOptionalOnlyCaller, SignaturesValidator { mapping(address => bool) private _isOnlyCallerEnabled; bytes32 private constant _SET_ONLY_CALLER_CHECK_TYPEHASH = keccak256( "SetOnlyCallerCheck(address user,bool enabled,uint256 nonce)" ); /** * @dev Reverts if the verification mechanism is enabled and the given address is not the caller. * @param user - Address to validate as the only allowed caller, if the verification is enabled. */ modifier optionalOnlyCaller(address user) { _verifyCaller(user); _; } function setOnlyCallerCheck(bool enabled) external override { _setOnlyCallerCheck(msg.sender, enabled); } function setOnlyCallerCheckWithSignature( address user, bool enabled, bytes memory signature ) external override { bytes32 structHash = keccak256(abi.encode(_SET_ONLY_CALLER_CHECK_TYPEHASH, user, enabled, getNextNonce(user))); _ensureValidSignature(user, structHash, signature, Errors.INVALID_SIGNATURE); _setOnlyCallerCheck(user, enabled); } function _setOnlyCallerCheck(address user, bool enabled) private { _isOnlyCallerEnabled[user] = enabled; emit OnlyCallerOptIn(user, enabled); } function isOnlyCallerEnabled(address user) external view override returns (bool) { return _isOnlyCallerEnabled[user]; } function _verifyCaller(address user) private view { if (_isOnlyCallerEnabled[user]) { _require(msg.sender == user, Errors.SENDER_NOT_ALLOWED); } } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC1271.sol"; import "./EOASignaturesValidator.sol"; import "../openzeppelin/Address.sol"; /** * @dev Utility for signing Solidity function calls. */ abstract contract SignaturesValidator is EOASignaturesValidator { using Address for address; function _isValidSignature( address account, bytes32 digest, bytes memory signature ) internal view virtual override returns (bool) { if (account.isContract()) { return IERC1271(account).isValidSignature(digest, signature) == IERC1271.isValidSignature.selector; } else { return super._isValidSignature(account, digest, signature); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow checks. * Adapted from OpenZeppelin's SafeMath library. */ library Math { // solhint-disable no-inline-assembly /** * @dev Returns the absolute value of a signed integer. */ function abs(int256 a) internal pure returns (uint256 result) { // Equivalent to: // result = a > 0 ? uint256(a) : uint256(-a) assembly { let s := sar(255, a) result := sub(xor(a, s), s) } } /** * @dev Returns the addition of two unsigned integers of 256 bits, reverting on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; _require(c >= a, Errors.ADD_OVERFLOW); return c; } /** * @dev Returns the addition of two signed integers, reverting on overflow. */ function add(int256 a, int256 b) internal pure returns (int256) { int256 c = a + b; _require((b >= 0 && c >= a) || (b < 0 && c < a), Errors.ADD_OVERFLOW); return c; } /** * @dev Returns the subtraction of two unsigned integers of 256 bits, reverting on overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { _require(b <= a, Errors.SUB_OVERFLOW); uint256 c = a - b; return c; } /** * @dev Returns the subtraction of two signed integers, reverting on overflow. */ function sub(int256 a, int256 b) internal pure returns (int256) { int256 c = a - b; _require((b >= 0 && c <= a) || (b < 0 && c > a), Errors.SUB_OVERFLOW); return c; } /** * @dev Returns the largest of two numbers of 256 bits. */ function max(uint256 a, uint256 b) internal pure returns (uint256 result) { // Equivalent to: // result = (a < b) ? b : a; assembly { result := sub(a, mul(sub(a, b), lt(a, b))) } } /** * @dev Returns the smallest of two numbers of 256 bits. */ function min(uint256 a, uint256 b) internal pure returns (uint256 result) { // Equivalent to `result = (a < b) ? a : b` assembly { result := sub(a, mul(sub(a, b), gt(a, b))) } } function mul(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a * b; _require(a == 0 || c / a == b, Errors.MUL_OVERFLOW); return c; } function div( uint256 a, uint256 b, bool roundUp ) internal pure returns (uint256) { return roundUp ? divUp(a, b) : divDown(a, b); } function divDown(uint256 a, uint256 b) internal pure returns (uint256) { _require(b != 0, Errors.ZERO_DIVISION); return a / b; } function divUp(uint256 a, uint256 b) internal pure returns (uint256 result) { _require(b != 0, Errors.ZERO_DIVISION); // Equivalent to: // result = a == 0 ? 0 : 1 + (a - 1) / b; assembly { result := mul(iszero(iszero(a)), add(1, div(sub(a, 1), b))) } } } // SPDX-License-Identifier: MIT // Based on the Address library from OpenZeppelin Contracts, altered by removing the `isContract` checks on // `functionCall` and `functionDelegateCall` in order to save gas, as the recipients are known to be contracts. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } // solhint-disable max-line-length /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { _require(address(this).balance >= amount, Errors.ADDRESS_INSUFFICIENT_BALANCE); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); _require(success, Errors.ADDRESS_CANNOT_SEND_VALUE); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call(data); return verifyCallResult(success, returndata); } // solhint-enable max-line-length /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but passing some native ETH as msg.value to the call. * * _Available since v3.4._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: value }(data); return verifyCallResult(success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResult(success, returndata); } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling up the * revert reason or using the one provided. * * _Available since v4.3._ */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { _revert(Errors.LOW_LEVEL_CALL_FAILED); } } } } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. * * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding * they need in their contracts using a combination of `abi.encode` and `keccak256`. * * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA * ({_hashTypedDataV4}). * * The implementation of the domain separator was designed to be as efficient as possible while still properly updating * the chain id to protect against replay attacks on an eventual fork of the chain. * * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. * * _Available since v3.4._ */ abstract contract EIP712 { /* solhint-disable var-name-mixedcase */ bytes32 private immutable _HASHED_NAME; bytes32 private immutable _HASHED_VERSION; bytes32 private immutable _TYPE_HASH; /* solhint-enable var-name-mixedcase */ /** * @dev Initializes the domain separator and parameter caches. * * The meaning of `name` and `version` is specified in * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: * * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. * - `version`: the current major version of the signing domain. * * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart * contract upgrade]. */ constructor(string memory name, string memory version) { _HASHED_NAME = keccak256(bytes(name)); _HASHED_VERSION = keccak256(bytes(version)); _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); } /** * @dev Returns the domain separator for the current chain. */ function _domainSeparatorV4() internal view virtual returns (bytes32) { return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, _getChainId(), address(this))); } /** * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this * function returns the hash of the fully encoded EIP712 message for this domain. * * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: * * ```solidity * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( * keccak256("Mail(address to,string contents)"), * mailTo, * keccak256(bytes(mailContents)) * ))); * address signer = ECDSA.recover(digest, signature); * ``` */ function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { return keccak256(abi.encodePacked("\\x19\\x01", _domainSeparatorV4(), structHash)); } // solc-ignore-next-line func-mutability function _getChainId() private view returns (uint256 chainId) { // solhint-disable-next-line no-inline-assembly assembly { chainId := chainid() } } } // SPDX-License-Identifier: MIT // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce bytecode size. // Modifier code is inlined by the compiler, which causes its code to appear multiple times in the codebase. By using // private functions, we achieve the same end result with slightly higher runtime gas costs, but reduced bytecode size. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { _enterNonReentrant(); _; _exitNonReentrant(); } function _enterNonReentrant() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED _require(_status != _ENTERED, Errors.REENTRANCY); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _exitNonReentrant() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } } // SPDX-License-Identifier: MIT // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce gas costs. // The `safeTransfer` and `safeTransferFrom` functions assume that `token` is a contract (an account with code), and // work differently from the OpenZeppelin version if it is not. pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { function safeApprove( IERC20 token, address to, uint256 value ) internal { // Some contracts need their allowance reduced to 0 before setting it to an arbitrary amount. if (value != 0 && token.allowance(address(this), address(to)) != 0) { _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, 0)); } _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, value)); } function safeTransfer( IERC20 token, address to, uint256 value ) internal { _callOptionalReturn(address(token), abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom( IERC20 token, address from, address to, uint256 value ) internal { _callOptionalReturn(address(token), abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * * WARNING: `token` is assumed to be a contract: calls to EOAs will *not* revert. */ function _callOptionalReturn(address token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = token.call(data); // If the low-level call didn't succeed we return whatever was returned from it. // solhint-disable-next-line no-inline-assembly assembly { if eq(success, 0) { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } // Finally we check the returndata size is either zero or true - note that this check will always pass for EOAs _require(returndata.length == 0 || abi.decode(returndata, (bool)), Errors.SAFE_ERC20_CALL_FAILED); } } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; _require(c >= a, Errors.ADD_OVERFLOW); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, Errors.SUB_OVERFLOW); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub( uint256 a, uint256 b, uint256 errorCode ) internal pure returns (uint256) { _require(b <= a, errorCode); uint256 c = a - b; return c; } } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; import "./IVotingEscrow.sol"; /** * @title Reward Distributor * @notice Distributes any tokens transferred to the contract (e.g. Protocol rewards and any token emissions) among veBPT * holders proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract. * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`. */ interface IRewardDistributor { event TokenCheckpointed( IERC20 token, uint256 amount, uint256 lastCheckpointTimestamp ); event TokensClaimed( address user, IERC20 token, uint256 amount, uint256 userTokenTimeCursor ); event TokenAdded(address indexed token); event RewardDeposit(IERC20 token, uint256 amount); event NewAdmin(address indexed newAdmin); /** * @notice Returns the VotingEscrow (veBPT) token contract */ function getVotingEscrow() external view returns (IVotingEscrow); /** * @notice Returns the global time cursor representing the most earliest uncheckpointed week. */ function getTimeCursor() external view returns (uint256); /** * @notice Returns the user-level time cursor representing the most earliest uncheckpointed week. * @param user - The address of the user to query. */ function getUserTimeCursor(address user) external view returns (uint256); /** * @notice Returns the token-level time cursor storing the timestamp at up to which tokens have been distributed. * @param token - The ERC20 token address to query. */ function getTokenTimeCursor(IERC20 token) external view returns (uint256); /** * @notice Returns the user-level time cursor storing the timestamp of the latest token distribution claimed. * @param user - The address of the user to query. * @param token - The ERC20 token address to query. */ function getUserTokenTimeCursor( address user, IERC20 token ) external view returns (uint256); /** * @notice Returns the user's cached balance of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires `user` to have been checkpointed past `timestamp` so that their balance is cached. * @param user - The address of the user of which to read the cached balance of. * @param timestamp - The timestamp at which to read the `user`'s cached balance at. */ function getUserBalanceAtTimestamp( address user, uint256 timestamp ) external view returns (uint256); /** * @notice Returns the cached total supply of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached. * @param timestamp - The timestamp at which to read the cached total supply at. */ function getTotalSupplyAtTimestamp( uint256 timestamp ) external view returns (uint256); /** * @notice Returns the RewardDistributor's cached balance of `token`. */ function getTokenLastBalance(IERC20 token) external view returns (uint256); /** * @notice Returns the amount of `token` which the RewardDistributor received in the week beginning at `timestamp`. * @param token - The ERC20 token address to query. * @param timestamp - The timestamp corresponding to the beginning of the week of interest. */ function getTokensDistributedInWeek( IERC20 token, uint256 timestamp ) external view returns (uint256); // Depositing /** * @notice Deposits tokens to be distributed in the current week. * @dev Sending tokens directly to the RewardDistributor instead of using `depositTokens` may result in tokens being * retroactively distributed to past weeks, or for the distribution to carry over to future weeks. * * If for some reason `depositTokens` cannot be called, in order to ensure that all tokens are correctly distributed * manually call `checkpointToken` before and after the token transfer. * @param token - The ERC20 token address to distribute. * @param amount - The amount of tokens to deposit. */ function depositToken(IERC20 token, uint256 amount) external; /** * @notice Deposits tokens to be distributed in the current week. * @dev A version of `depositToken` which supports depositing multiple `tokens` at once. * See `depositToken` for more details. * @param tokens - An array of ERC20 token addresses to distribute. * @param amounts - An array of token amounts to deposit. */ function depositTokens( IERC20[] calldata tokens, uint256[] calldata amounts ) external; // Checkpointing /** * @notice Caches the total supply of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. */ function checkpoint() external; /** * @notice Caches the user's balance of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param user - The address of the user to be checkpointed. */ function checkpointUser(address user) external; /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev Any `token` balance held by the RewardDistributor above that which is returned by `getTokenLastBalance` * will be distributed evenly across the time period since `token` was last checkpointed. * * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param token - The ERC20 token address to be checkpointed. */ function checkpointToken(IERC20 token) external; /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev A version of `checkpointToken` which supports checkpointing multiple tokens. * See `checkpointToken` for more details. * @param tokens - An array of ERC20 token addresses to be checkpointed. */ function checkpointTokens(IERC20[] calldata tokens) external; // Claiming /** * @notice Claims all pending distributions of the provided token for a user. * @dev It's not necessary to explicitly checkpoint before calling this function, it will ensure the RewardDistributor * is up to date before calculating the amount of tokens to be claimed. * @param user - The user on behalf of which to claim. * @param token - The ERC20 token address to be claimed. * @return The amount of `token` sent to `user` as a result of claiming. */ function claimToken(address user, IERC20 token) external returns (uint256); /** * @notice Claims a number of tokens on behalf of a user. * @dev A version of `claimToken` which supports claiming multiple `tokens` on behalf of `user`. * See `claimToken` for more details. * @param user - The user on behalf of which to claim. * @param tokens - An array of ERC20 token addresses to be claimed. * @return An array of the amounts of each token in `tokens` sent to `user` as a result of claiming. */ function claimTokens( address user, IERC20[] calldata tokens ) external returns (uint256[] memory); } // SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface IRewardFaucet { function distributePastRewards(address rewardToken) external; } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; // solhint-disable func-name-mixedcase interface IVotingEscrow { struct Point { int128 bias; int128 slope; // - dweight / dt uint256 ts; uint256 blk; // block } function epoch() external view returns (uint256); function admin() external view returns (address); function future_admin() external view returns (address); function apply_smart_wallet_checker() external; function apply_transfer_ownership() external; // function balanceOf(address addr, uint256 _t) external view returns (uint256); function balanceOf( address user, uint256 timestamp ) external view returns (uint256); function balanceOfAt( address addr, uint256 _block ) external view returns (uint256); function checkpoint() external; function commit_smart_wallet_checker(address addr) external; function commit_transfer_ownership(address addr) external; function create_lock(uint256 _value, uint256 _unlock_time) external; function decimals() external view returns (uint256); function deposit_for(address _addr, uint256 _value) external; function get_last_user_slope(address addr) external view returns (int128); function increase_amount(uint256 _value) external; function increase_unlock_time(uint256 _unlock_time) external; function locked__end(address _addr) external view returns (uint256); function name() external view returns (string memory); function point_history( uint256 timestamp ) external view returns (Point memory); function symbol() external view returns (string memory); function token() external view returns (address); function totalSupply(uint256 t) external view returns (uint256); function totalSupplyAt(uint256 _block) external view returns (uint256); function user_point_epoch(address user) external view returns (uint256); function user_point_history__ts( address _addr, uint256 _idx ) external view returns (uint256); function user_point_history( address user, uint256 timestamp ) external view returns (Point memory); function withdraw() external; } // SPDX-License-Identifier: GPL-3.0-or-later // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.7.0; pragma experimental ABIEncoderV2; import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol"; import {IRewardDistributor} from "./interfaces/IRewardDistributor.sol"; import {IRewardFaucet} from "./interfaces/IRewardFaucet.sol"; import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ReentrancyGuard.sol"; import "@balancer-labs/v2-solidity-utils/contracts/helpers/OptionalOnlyCaller.sol"; import "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol"; import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol"; import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeMath.sol"; import "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol"; // solhint-disable not-rely-on-time /** * @title Reward Distributor * @notice Distributes any tokens transferred to the contract among veBPT holders * proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract. * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`. */ contract RewardDistributor is IRewardDistributor, OptionalOnlyCaller, ReentrancyGuard { using SafeMath for uint256; using SafeERC20 for IERC20; bool public isInitialized; IVotingEscrow private _votingEscrow; IRewardFaucet public rewardFaucet; uint256 private _startTime; // Global State uint256 private _timeCursor; mapping(uint256 => uint256) private _veSupplyCache; address public admin; address[] private _rewardTokens; mapping(address => bool) public allowedRewardTokens; // Token State // `startTime` and `timeCursor` are both timestamps so comfortably fit in a uint64. // `cachedBalance` will comfortably fit the total supply of any meaningful token. // Should more than 2^128 tokens be sent to this contract then checkpointing this token will fail until enough // tokens have been claimed to bring the total balance back below 2^128. struct TokenState { uint64 startTime; uint64 timeCursor; uint128 cachedBalance; } mapping(IERC20 => TokenState) private _tokenState; mapping(IERC20 => mapping(uint256 => uint256)) private _tokensPerWeek; // User State // `startTime` and `timeCursor` are timestamps so will comfortably fit in a uint64. // For `lastEpochCheckpointed` to overflow would need over 2^128 transactions to the VotingEscrow contract. struct UserState { uint64 startTime; uint64 timeCursor; uint128 lastEpochCheckpointed; } mapping(address => UserState) internal _userState; mapping(address => mapping(uint256 => uint256)) private _userBalanceAtTimestamp; mapping(address => mapping(IERC20 => uint256)) private _userTokenTimeCursor; constructor() EIP712("RewardDistributor", "1") {} modifier onlyAdmin() { require(admin == msg.sender, "not admin"); _; } function initialize( IVotingEscrow votingEscrow, IRewardFaucet rewardFaucet_, uint256 startTime, address admin_ ) external { require(!isInitialized, "!twice"); isInitialized = true; require(admin_ != address(0) && address(rewardFaucet_) != address(0), "!zero"); admin = admin_; rewardFaucet = rewardFaucet_; _votingEscrow = votingEscrow; startTime = _roundDownTimestamp(startTime); uint256 currentWeek = _roundDownTimestamp(block.timestamp); require(startTime >= currentWeek, "Cannot start before current week"); require(startTime <= currentWeek + 10 weeks, "10 weeks delay max"); if (startTime == currentWeek) { // We assume that `votingEscrow` has been deployed in a week previous to this one. // If `votingEscrow` did not have a non-zero supply at the beginning of the current week // then any tokens which are distributed this week will be lost permanently. require( votingEscrow.totalSupply(currentWeek) > 0, "Zero total supply results in lost tokens" ); } _startTime = startTime; _timeCursor = startTime; } /** * @notice Returns the VotingEscrow (veBPT) token contract */ function getVotingEscrow() external view override returns (IVotingEscrow) { return _votingEscrow; } /** * @notice Returns the global time cursor representing the most earliest uncheckpointed week. */ function getTimeCursor() external view override returns (uint256) { return _timeCursor; } /** * @notice Returns the user-level time cursor representing the most earliest uncheckpointed week. * @param user - The address of the user to query. */ function getUserTimeCursor( address user ) external view override returns (uint256) { return _userState[user].timeCursor; } /** * @notice Returns the token-level time cursor storing the timestamp at up to which tokens have been distributed. * @param token - The ERC20 token address to query. */ function getTokenTimeCursor( IERC20 token ) external view override returns (uint256) { return _tokenState[token].timeCursor; } /** * @notice Returns the user-level time cursor storing the timestamp of the latest token distribution claimed. * @param user - The address of the user to query. * @param token - The ERC20 token address to query. */ function getUserTokenTimeCursor( address user, IERC20 token ) external view override returns (uint256) { return _getUserTokenTimeCursor(user, token); } /** * @notice Returns the user's cached balance of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires `user` to have been checkpointed past `timestamp` so that their balance is cached. * @param user - The address of the user of which to read the cached balance of. * @param timestamp - The timestamp at which to read the `user`'s cached balance at. */ function getUserBalanceAtTimestamp( address user, uint256 timestamp ) external view override returns (uint256) { return _userBalanceAtTimestamp[user][timestamp]; } /** * @notice Returns the cached total supply of veBPT as of the provided timestamp. * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values. * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached. * @param timestamp - The timestamp at which to read the cached total supply at. */ function getTotalSupplyAtTimestamp( uint256 timestamp ) external view override returns (uint256) { return _veSupplyCache[timestamp]; } /** * @notice Returns the RewardDistributor's cached balance of `token`. */ function getTokenLastBalance( IERC20 token ) external view override returns (uint256) { return _tokenState[token].cachedBalance; } /** * @notice Returns the amount of `token` which the RewardDistributor received in the week beginning at `timestamp`. * @param token - The ERC20 token address to query. * @param timestamp - The timestamp corresponding to the beginning of the week of interest. */ function getTokensDistributedInWeek( IERC20 token, uint256 timestamp ) external view override returns (uint256) { return _tokensPerWeek[token][timestamp]; } // Depositing /** * @notice Deposits tokens to be distributed in the current week. * @dev Sending tokens directly to the RewardDistributor instead of using `depositToken` may result in tokens being * retroactively distributed to past weeks, or for the distribution to carry over to future weeks. * * If for some reason `depositToken` cannot be called, in order to ensure that all tokens are correctly distributed * manually call `checkpointToken` before and after the token transfer. * @param token - The ERC20 token address to distribute. * @param amount - The amount of tokens to deposit. */ function depositToken( IERC20 token, uint256 amount ) external override nonReentrant { require(allowedRewardTokens[address(token)], "!allowed"); _checkpointToken(token, false); token.safeTransferFrom(msg.sender, address(this), amount); _checkpointToken(token, true); emit RewardDeposit(token, amount); } /** * @notice Deposits tokens by faucet to be distributed in the current week. * @dev Sending tokens directly to the RewardDistributor instead of using `depositToken` may result in tokens being * retroactively distributed to past weeks, or for the distribution to carry over to future weeks. * * If for some reason `depositToken` cannot be called, in order to ensure that all tokens are correctly distributed * manually call `checkpointToken` before and after the token transfer. * @param token - The ERC20 token address to distribute. * @param amount - The amount of tokens to deposit. */ function faucetDepositToken( IERC20 token, uint256 amount ) external { require(allowedRewardTokens[address(token)], "!allowed"); require(msg.sender == address(rewardFaucet), "only faucet"); _checkpointToken(token, false); token.safeTransferFrom(msg.sender, address(this), amount); _checkpointToken(token, true); } /** * @notice Deposits tokens to be distributed in the current week. * @dev A version of `depositToken` which supports depositing multiple `tokens` at once. * See `depositToken` for more details. * @param tokens - An array of ERC20 token addresses to distribute. * @param amounts - An array of token amounts to deposit. */ function depositTokens( IERC20[] calldata tokens, uint256[] calldata amounts ) external override nonReentrant { InputHelpers.ensureInputLengthMatch(tokens.length, amounts.length); uint256 length = tokens.length; for (uint256 i = 0; i < length; ++i) { require(allowedRewardTokens[address(tokens[i])], "!allowed"); _checkpointToken(tokens[i], false); tokens[i].safeTransferFrom(msg.sender, address(this), amounts[i]); _checkpointToken(tokens[i], true); emit RewardDeposit(tokens[i], amounts[i]); } } // Checkpointing /** * @notice Caches the total supply of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. */ function checkpoint() external override nonReentrant { _checkpointTotalSupply(); } /** * @notice Caches the user's balance of veBPT at the beginning of each week. * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param user - The address of the user to be checkpointed. */ function checkpointUser(address user) external override nonReentrant { _checkpointUserBalance(user); } /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev Any `token` balance held by the RewardDistributor above that which is returned by `getTokenLastBalance` * will be distributed evenly across the time period since `token` was last checkpointed. * * This function will be called automatically before claiming tokens to ensure the contract is properly updated. * @param token - The ERC20 token address to be checkpointed. */ function checkpointToken(IERC20 token) external override nonReentrant { require(allowedRewardTokens[address(token)], "!allowed"); _checkpointToken(token, true); } /** * @notice Assigns any newly-received tokens held by the RewardDistributor to weekly distributions. * @dev A version of `checkpointToken` which supports checkpointing multiple tokens. * See `checkpointToken` for more details. * @param tokens - An array of ERC20 token addresses to be checkpointed. */ function checkpointTokens( IERC20[] calldata tokens ) external override nonReentrant { uint256 tokensLength = tokens.length; for (uint256 i = 0; i < tokensLength; ++i) { require(allowedRewardTokens[address(tokens[i])], "!allowed"); _checkpointToken(tokens[i], true); } } // Claiming /** * @notice Claims all pending distributions of the provided token for a user. * @dev It's not necessary to explicitly checkpoint before calling this function, it will ensure the RewardDistributor * is up to date before calculating the amount of tokens to be claimed. * @param user - The user on behalf of which to claim. * @param token - The ERC20 token address to be claimed. * @return The amount of `token` sent to `user` as a result of claiming. */ function claimToken( address user, IERC20 token ) external override nonReentrant optionalOnlyCaller(user) returns (uint256) { require(allowedRewardTokens[address(token)], "!allowed"); _checkpointTotalSupply(); _checkpointUserBalance(user); _checkpointToken(token, false); uint256 amount = _claimToken(user, token); rewardFaucet.distributePastRewards(address(token)); return amount; } /** * @notice Claims a number of tokens on behalf of a user. * @dev A version of `claimToken` which supports claiming multiple `tokens` on behalf of `user`. * See `claimToken` for more details. * @param user - The user on behalf of which to claim. * @param tokens - An array of ERC20 token addresses to be claimed. * @return An array of the amounts of each token in `tokens` sent to `user` as a result of claiming. */ function claimTokens( address user, IERC20[] calldata tokens ) external override nonReentrant optionalOnlyCaller(user) returns (uint256[] memory) { _checkpointTotalSupply(); _checkpointUserBalance(user); uint256 tokensLength = tokens.length; uint256[] memory amounts = new uint256[](tokensLength); for (uint256 i = 0; i < tokensLength; ++i) { require(allowedRewardTokens[address(tokens[i])], "!allowed"); _checkpointToken(tokens[i], false); amounts[i] = _claimToken(user, tokens[i]); rewardFaucet.distributePastRewards(address(tokens[i])); } return amounts; } // Internal functions /** * @dev It is required that both the global, token and user state have been properly checkpointed * before calling this function. */ function _claimToken( address user, IERC20 token ) internal returns (uint256) { TokenState storage tokenState = _tokenState[token]; uint256 nextUserTokenWeekToClaim = _getUserTokenTimeCursor(user, token); // The first week which cannot be correctly claimed is the earliest of: // - A) The global or user time cursor (whichever is earliest), rounded up to the end of the week. // - B) The token time cursor, rounded down to the beginning of the week. // // This prevents the two failure modes: // - A) A user may claim a week for which we have not processed their balance, resulting in tokens being locked. // - B) A user may claim a week which then receives more tokens to be distributed. However the user has // already claimed for that week so their share of these new tokens are lost. uint256 firstUnclaimableWeek = Math.min( _roundUpTimestamp( Math.min(_timeCursor, _userState[user].timeCursor) ), _roundDownTimestamp(tokenState.timeCursor) ); mapping(uint256 => uint256) storage tokensPerWeek = _tokensPerWeek[ token ]; mapping(uint256 => uint256) storage userBalanceAtTimestamp = _userBalanceAtTimestamp[user]; uint256 amount; for (uint256 i = 0; i < 20; ++i) { // We clearly cannot claim for `firstUnclaimableWeek` and so we break here. if (nextUserTokenWeekToClaim >= firstUnclaimableWeek) break; if (_veSupplyCache[nextUserTokenWeekToClaim] == 0) break; amount += (tokensPerWeek[nextUserTokenWeekToClaim] * userBalanceAtTimestamp[nextUserTokenWeekToClaim]) / _veSupplyCache[nextUserTokenWeekToClaim]; nextUserTokenWeekToClaim += 1 weeks; } // Update the stored user-token time cursor to prevent this user claiming this week again. _userTokenTimeCursor[user][token] = nextUserTokenWeekToClaim; if (amount > 0) { // For a token to be claimable it must have been added to the cached balance so this is safe. tokenState.cachedBalance = uint128( tokenState.cachedBalance - amount ); token.safeTransfer(user, amount); emit TokensClaimed(user, token, amount, nextUserTokenWeekToClaim); } return amount; } /** * @dev Calculate the amount of `token` to be distributed to `_votingEscrow` holders since the last checkpoint. */ function _checkpointToken(IERC20 token, bool force) internal { TokenState storage tokenState = _tokenState[token]; uint256 lastTokenTime = tokenState.timeCursor; uint256 timeSinceLastCheckpoint; if (lastTokenTime == 0) { // If it's the first time we're checkpointing this token then start distributing from now. // Also mark at which timestamp users should start attempts to claim this token from. lastTokenTime = block.timestamp; tokenState.startTime = uint64(_roundDownTimestamp(block.timestamp)); // Prevent someone from assigning tokens to an inaccessible week. require( block.timestamp > _startTime, "Reward distribution has not started yet" ); } else { timeSinceLastCheckpoint = block.timestamp - lastTokenTime; if (!force) { // Checkpointing N times within a single week is completely equivalent to checkpointing once at the end. // We then want to get as close as possible to a single checkpoint every Wed 23:59 UTC to save gas. // We then skip checkpointing if we're in the same week as the previous checkpoint. bool alreadyCheckpointedThisWeek = _roundDownTimestamp( block.timestamp ) == _roundDownTimestamp(lastTokenTime); // However we want to ensure that all of this week's rewards are assigned to the current week without // overspilling into the next week. To mitigate this, we checkpoint if we're near the end of the week. bool nearingEndOfWeek = _roundUpTimestamp(block.timestamp) - block.timestamp < 1 days; // This ensures that we checkpoint once at the beginning of the week and again for each user interaction // towards the end of the week to give an accurate final reading of the balance. if (alreadyCheckpointedThisWeek && !nearingEndOfWeek) { return; } } } tokenState.timeCursor = uint64(block.timestamp); uint256 tokenBalance = token.balanceOf(address(this)); uint256 newTokensToDistribute = tokenBalance.sub( tokenState.cachedBalance ); if (newTokensToDistribute == 0) return; require( tokenBalance <= type(uint128).max, "Maximum token balance exceeded" ); uint256 firstIncompleteWeek = _roundDownTimestamp(lastTokenTime); uint256 nextWeek = 0; // Distribute `newTokensToDistribute` evenly across the time period from `lastTokenTime` to now. // These tokens are assigned to weeks proportionally to how much of this period falls into each week. mapping(uint256 => uint256) storage tokensPerWeek = _tokensPerWeek[ token ]; uint256 amountToAdd; for (uint256 i = 0; i < 20; ++i) { // This is safe as we're incrementing a timestamp. nextWeek = firstIncompleteWeek + 1 weeks; if (block.timestamp < nextWeek) { // `firstIncompleteWeek` is now the beginning of the current week, i.e. this is the final iteration. if ( timeSinceLastCheckpoint == 0 && block.timestamp == lastTokenTime ) { amountToAdd = newTokensToDistribute; } else { // block.timestamp >= lastTokenTime by definition. amountToAdd = (newTokensToDistribute * (block.timestamp - lastTokenTime)) / timeSinceLastCheckpoint; } if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) { tokensPerWeek[firstIncompleteWeek] += amountToAdd; tokenState.cachedBalance += uint128(amountToAdd); } // As we've caught up to the present then we should now break. break; } else { // We've gone a full week or more without checkpointing so need to distribute tokens to previous weeks. if (timeSinceLastCheckpoint == 0 && nextWeek == lastTokenTime) { // It shouldn't be possible to enter this block amountToAdd = newTokensToDistribute; } else { // nextWeek > lastTokenTime by definition. amountToAdd = (newTokensToDistribute * (nextWeek - lastTokenTime)) / timeSinceLastCheckpoint; } } if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) { tokensPerWeek[firstIncompleteWeek] += amountToAdd; tokenState.cachedBalance += uint128(amountToAdd); } // We've now "checkpointed" up to the beginning of next week so must update timestamps appropriately. lastTokenTime = nextWeek; firstIncompleteWeek = nextWeek; } emit TokenCheckpointed(token, newTokensToDistribute, lastTokenTime); } /** * @dev Cache the `user`'s balance of `_votingEscrow` at the beginning of each new week */ function _checkpointUserBalance(address user) internal { uint256 maxUserEpoch = _votingEscrow.user_point_epoch(user); // If user has no epochs then they have never locked veBPT. // They clearly will not then receive rewards. if (maxUserEpoch == 0) return; UserState storage userState = _userState[user]; // `nextWeekToCheckpoint` represents the timestamp of the beginning of the first week // which we haven't checkpointed the user's VotingEscrow balance yet. uint256 nextWeekToCheckpoint = userState.timeCursor; uint256 userEpoch; if (nextWeekToCheckpoint == 0) { // First checkpoint for user so need to do the initial binary search userEpoch = _findTimestampUserEpoch( user, _startTime, 0, maxUserEpoch ); } else { if (nextWeekToCheckpoint >= block.timestamp) { // User has checkpointed the current week already so perform early return. // This prevents a user from processing epochs created later in this week, however this is not an issue // as if a significant number of these builds up then the user will skip past them with a binary search. return; } // Otherwise use the value saved from last time userEpoch = userState.lastEpochCheckpointed; // This optimizes a scenario common for power users, which have frequent `VotingEscrow` interactions in // the same week. We assume that any such user is also claiming rewards every week, and so we only perform // a binary search here rather than integrating it into the main search algorithm, effectively skipping // most of the week's irrelevant checkpoints. // The slight tradeoff is that users who have multiple infrequent `VotingEscrow` interactions and also don't // claim frequently will also perform the binary search, despite it not leading to gas savings. if (maxUserEpoch - userEpoch > 20) { userEpoch = _findTimestampUserEpoch( user, nextWeekToCheckpoint, userEpoch, maxUserEpoch ); } } // Epoch 0 is always empty so bump onto the next one so that we start on a valid epoch. if (userEpoch == 0) { userEpoch = 1; } IVotingEscrow.Point memory nextUserPoint = _votingEscrow .user_point_history(user, userEpoch); // If this is the first checkpoint for the user, calculate the first week they're eligible for. // i.e. the timestamp of the first Thursday after they locked. // If this is earlier then the first distribution then fast forward to then. if (nextWeekToCheckpoint == 0) { // Disallow checkpointing before `startTime`. require( block.timestamp > _startTime, "Reward distribution has not started yet" ); nextWeekToCheckpoint = Math.max( _startTime, _roundUpTimestamp(nextUserPoint.ts) ); userState.startTime = uint64(nextWeekToCheckpoint); } // It's safe to increment `userEpoch` and `nextWeekToCheckpoint` in this loop as epochs and timestamps // are always much smaller than 2^256 and are being incremented by small values. IVotingEscrow.Point memory currentUserPoint; for (uint256 i = 0; i < 50; ++i) { if ( nextWeekToCheckpoint >= nextUserPoint.ts && userEpoch <= maxUserEpoch ) { // The week being considered is contained in a user epoch after that described by `currentUserPoint`. // We then shift `nextUserPoint` into `currentUserPoint` and query the Point for the next user epoch. // We do this in order to step though epochs until we find the first epoch starting after // `nextWeekToCheckpoint`, making the previous epoch the one that contains `nextWeekToCheckpoint`. userEpoch += 1; currentUserPoint = nextUserPoint; if (userEpoch > maxUserEpoch) { nextUserPoint = IVotingEscrow.Point(0, 0, 0, 0); } else { nextUserPoint = _votingEscrow.user_point_history( user, userEpoch ); } } else { // The week being considered lies inside the user epoch described by `oldUserPoint` // we can then use it to calculate the user's balance at the beginning of the week. if (nextWeekToCheckpoint >= block.timestamp) { // Break if we're trying to cache the user's balance at a timestamp in the future. // We only perform this check here to ensure that we can still process checkpoints created // in the current week. break; } int128 dt = int128(nextWeekToCheckpoint - currentUserPoint.ts); uint256 userBalance = currentUserPoint.bias > currentUserPoint.slope * dt ? uint256( currentUserPoint.bias - currentUserPoint.slope * dt ) : 0; // User's lock has expired and they haven't relocked yet. if (userBalance == 0 && userEpoch > maxUserEpoch) { nextWeekToCheckpoint = _roundUpTimestamp(block.timestamp); break; } // User had a nonzero lock and so is eligible to collect rewards. _userBalanceAtTimestamp[user][ nextWeekToCheckpoint ] = userBalance; nextWeekToCheckpoint += 1 weeks; } } // We subtract off 1 from the userEpoch to step back once so that on the next attempt to checkpoint // the current `currentUserPoint` will be loaded as `nextUserPoint`. This ensures that we can't skip over the // user epoch containing `nextWeekToCheckpoint`. // userEpoch > 0 so this is safe. userState.lastEpochCheckpointed = uint64(userEpoch - 1); userState.timeCursor = uint64(nextWeekToCheckpoint); } /** * @dev Cache the totalSupply of VotingEscrow token at the beginning of each new week */ function _checkpointTotalSupply() internal { uint256 nextWeekToCheckpoint = _timeCursor; uint256 weekStart = _roundDownTimestamp(block.timestamp); // We expect `timeCursor == weekStart + 1 weeks` when fully up to date. if (nextWeekToCheckpoint > weekStart || weekStart == block.timestamp) { // We've already checkpointed up to this week so perform early return return; } _votingEscrow.checkpoint(); // Step through the each week and cache the total supply at beginning of week on this contract for (uint256 i = 0; i < 20; ++i) { if (nextWeekToCheckpoint > weekStart) break; _veSupplyCache[nextWeekToCheckpoint] = _votingEscrow.totalSupply( nextWeekToCheckpoint ); // This is safe as we're incrementing a timestamp nextWeekToCheckpoint += 1 weeks; } // Update state to the end of the current week (`weekStart` + 1 weeks) _timeCursor = nextWeekToCheckpoint; } // Helper functions /** * @dev Wrapper around `_userTokenTimeCursor` which returns the start timestamp for `token` * if `user` has not attempted to interact with it previously. */ function _getUserTokenTimeCursor( address user, IERC20 token ) internal view returns (uint256) { uint256 userTimeCursor = _userTokenTimeCursor[user][token]; if (userTimeCursor > 0) return userTimeCursor; // This is the first time that the user has interacted with this token. // We then start from the latest out of either when `user` first locked veBPT or `token` was first checkpointed. return Math.max(_userState[user].startTime, _tokenState[token].startTime); } /** * @dev Return the user epoch number for `user` corresponding to the provided `timestamp` */ function _findTimestampUserEpoch( address user, uint256 timestamp, uint256 minUserEpoch, uint256 maxUserEpoch ) internal view returns (uint256) { uint256 min = minUserEpoch; uint256 max = maxUserEpoch; // Perform binary search through epochs to find epoch containing `timestamp` for (uint256 i = 0; i < 128; ++i) { if (min >= max) break; // Algorithm assumes that inputs are less than 2^128 so this operation is safe. // +2 avoids getting stuck in min == mid < max uint256 mid = (min + max + 2) / 2; IVotingEscrow.Point memory pt = _votingEscrow .user_point_history(user, mid); if (pt.ts <= timestamp) { min = mid; } else { // max > min so this is safe. max = mid - 1; } } return min; } /** * @dev Rounds the provided timestamp down to the beginning of the previous week (Thurs 00:00 UTC) */ function _roundDownTimestamp( uint256 timestamp ) private pure returns (uint256) { // Division by zero or overflows are impossible here. return (timestamp / 1 weeks) * 1 weeks; } /** * @dev Rounds the provided timestamp up to the beginning of the next week (Thurs 00:00 UTC) */ function _roundUpTimestamp( uint256 timestamp ) private pure returns (uint256) { // Overflows are impossible here for all realistic inputs. return _roundDownTimestamp(timestamp + 1 weeks - 1); } /** * @notice Adds allowed tokens for the distribution. * @param tokens - An array of ERC20 token addresses to be added for the further reward distribution. */ function addAllowedRewardTokens(address[] calldata tokens) external onlyAdmin { for (uint256 i = 0; i < tokens.length; i++) { require(!allowedRewardTokens[tokens[i]], "already exist"); allowedRewardTokens[tokens[i]] = true; _rewardTokens.push(tokens[i]); emit TokenAdded(tokens[i]); } } /** * @notice Returns allowed for reward distribution tokens list. * @return An array of ERC20 token addresses which can be used as rewards. */ function getAllowedRewardTokens() external view returns (address[] memory) { return _rewardTokens; } /** * @notice Transfers admin rights to new address. * @param newAdmin - The new admin address to set. */ function transferAdmin(address newAdmin) external onlyAdmin { require (newAdmin != address(0), "zero address"); admin = newAdmin; emit NewAdmin(newAdmin); } }
File 4 of 7: Voting Escrow
# @version 0.3.7 """ @title Voting Escrow @author Curve Finance @license MIT @notice Votes have a weight depending on time, so that users are committed to the future of (whatever they are voting for) @dev Vote weight decays linearly over time. Lock time cannot be more than `MAXTIME` (set by creator). """ # Voting escrow to have time-weighted votes # Votes have a weight depending on time, so that users are committed # to the future of (whatever they are voting for). # The weight in this implementation is linear, and lock cannot be more than maxtime: # w ^ # 1 + / # | / # | / # | / # |/ # 0 +--------+------> time # maxtime struct Point: bias: int128 slope: int128 # - dweight / dt ts: uint256 blk: uint256 # block # We cannot really do block numbers per se b/c slope is per time, not per block # and per block could be fairly bad b/c Ethereum changes blocktimes. # What we can do is to extrapolate ***At functions struct LockedBalance: amount: int128 end: uint256 interface ERC20: def decimals() -> uint256: view def name() -> String[64]: view def symbol() -> String[32]: view def balanceOf(account: address) -> uint256: view def transfer(to: address, amount: uint256) -> bool: nonpayable def approve(spender: address, amount: uint256) -> bool: nonpayable def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable # Interface for checking whether address belongs to a whitelisted # type of a smart wallet. # When new types are added - the whole contract is changed # The check() method is modifying to be able to use caching # for individual wallet addresses interface SmartWalletChecker: def check(addr: address) -> bool: nonpayable interface BalancerMinter: def mint(gauge: address) -> uint256: nonpayable interface RewardDistributor: def depositToken(token: address, amount: uint256): nonpayable DEPOSIT_FOR_TYPE: constant(int128) = 0 CREATE_LOCK_TYPE: constant(int128) = 1 INCREASE_LOCK_AMOUNT: constant(int128) = 2 INCREASE_UNLOCK_TIME: constant(int128) = 3 event CommitOwnership: admin: address event ApplyOwnership: admin: address event EarlyUnlock: status: bool event PenaltySpeed: penalty_k: uint256 event PenaltyTreasury: penalty_treasury: address event TotalUnlock: status: bool event RewardReceiver: newReceiver: address event Deposit: provider: indexed(address) value: uint256 locktime: indexed(uint256) type: int128 ts: uint256 event Withdraw: provider: indexed(address) value: uint256 ts: uint256 event WithdrawEarly: provider: indexed(address) penalty: uint256 time_left: uint256 event Supply: prevSupply: uint256 supply: uint256 WEEK: constant(uint256) = 7 * 86400 # all future times are rounded by week MAXTIME: public(uint256) MULTIPLIER: constant(uint256) = 10**18 TOKEN: public(address) NAME: String[64] SYMBOL: String[32] DECIMALS: uint256 supply: public(uint256) locked: public(HashMap[address, LockedBalance]) epoch: public(uint256) point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch] user_point_epoch: public(HashMap[address, uint256]) slope_changes: public(HashMap[uint256, int128]) # time -> signed slope change # Checker for whitelisted (smart contract) wallets which are allowed to deposit # The goal is to prevent tokenizing the escrow future_smart_wallet_checker: public(address) smart_wallet_checker: public(address) admin: public(address) # unlock admins can be set only once. Zero-address means unlock is disabled admin_unlock_all: public(address) admin_early_unlock: public(address) future_admin: public(address) is_initialized: public(bool) early_unlock: public(bool) penalty_k: public(uint256) prev_penalty_k: public(uint256) penalty_upd_ts: public(uint256) PENALTY_COOLDOWN: constant(uint256) = 60 # cooldown to prevent font-run on penalty change PENALTY_MULTIPLIER: constant(uint256) = 10 penalty_treasury: public(address) balMinter: public(address) balToken: public(address) rewardReceiver: public(address) rewardReceiverChangeable: public(bool) rewardDistributor: public(address) all_unlock: public(bool) @external def initialize( _token_addr: address, _name: String[64], _symbol: String[32], _admin_addr: address, _admin_unlock_all: address, _admin_early_unlock: address, _max_time: uint256, _balToken: address, _balMinter: address, _rewardReceiver: address, _rewardReceiverChangeable: bool, _rewardDistributor: address ): """ @notice Contract constructor @param _token_addr 80/20 Token-WETH BPT token address @param _name Token name @param _symbol Token symbol @param _admin_addr Contract admin address @param _admin_unlock_all Admin to enable Unlock-All feature (zero-address to disable forever) @param _admin_early_unlock Admin to enable Eraly-Unlock feature (zero-address to disable forever) @param _max_time Locking max time @param _balToken Address of the Balancer token @param _balMinter Address of the Balancer minter @param _rewardReceiver Address of the reward receiver @param _rewardReceiverChangeable Boolean indicating whether the reward receiver is changeable @param _rewardDistributor The RewardDistributor contract address """ assert(not self.is_initialized), 'only once' self.is_initialized = True assert(_admin_addr != empty(address)), '!empty' self.admin = _admin_addr self.penalty_k = 10 self.prev_penalty_k = 10 self.penalty_upd_ts = block.timestamp self.penalty_treasury = _admin_addr self.TOKEN = _token_addr self.point_history[0].blk = block.number self.point_history[0].ts = block.timestamp _decimals: uint256 = ERC20(_token_addr).decimals() # also validates token for non-zero assert (_decimals >= 6 and _decimals <= 255), '!decimals' self.NAME = _name self.SYMBOL = _symbol self.DECIMALS = _decimals assert(_max_time >= WEEK and _max_time <= WEEK * 52 * 5), '!maxlock' self.MAXTIME = _max_time self.admin_unlock_all = _admin_unlock_all self.admin_early_unlock = _admin_early_unlock self.balToken = _balToken self.balMinter = _balMinter self.rewardReceiver = _rewardReceiver self.rewardReceiverChangeable = _rewardReceiverChangeable self.rewardDistributor = _rewardDistributor @external @view def token() -> address: return self.TOKEN @external @view def name() -> String[64]: return self.NAME @external @view def symbol() -> String[32]: return self.SYMBOL @external @view def decimals() -> uint256: return self.DECIMALS @external def commit_transfer_ownership(addr: address): """ @notice Transfer ownership of VotingEscrow contract to `addr` @param addr Address to have ownership transferred to """ assert msg.sender == self.admin # dev: admin only self.future_admin = addr log CommitOwnership(addr) @external def apply_transfer_ownership(): """ @notice Apply ownership transfer """ assert msg.sender == self.admin # dev: admin only _admin: address = self.future_admin assert _admin != empty(address) # dev: admin not set self.admin = _admin log ApplyOwnership(_admin) @external def commit_smart_wallet_checker(addr: address): """ @notice Set an external contract to check for approved smart contract wallets @param addr Address of Smart contract checker """ assert msg.sender == self.admin self.future_smart_wallet_checker = addr @external def apply_smart_wallet_checker(): """ @notice Apply setting external contract to check approved smart contract wallets """ assert msg.sender == self.admin self.smart_wallet_checker = self.future_smart_wallet_checker @internal def assert_not_contract(addr: address): """ @notice Check if the call is from a whitelisted smart contract, revert if not @param addr Address to be checked """ if addr != tx.origin: checker: address = self.smart_wallet_checker if checker != empty(address): if SmartWalletChecker(checker).check(addr): return raise "Smart contract depositors not allowed" @external def set_early_unlock(_early_unlock: bool): """ @notice Sets the availability for users to unlock their locks before lock-end with penalty @dev Only the admin_early_unlock can execute this function. @param _early_unlock A boolean indicating whether early unlock is allowed or not. """ assert msg.sender == self.admin_early_unlock, '!admin' # dev: admin_early_unlock only assert _early_unlock != self.early_unlock, 'already' self.early_unlock = _early_unlock log EarlyUnlock(_early_unlock) @external def set_early_unlock_penalty_speed(_penalty_k: uint256): """ @notice Sets penalty speed for early unlocking @dev Only the admin can execute this function. To prevent frontrunning we use PENALTY_COOLDOWN period @param _penalty_k Coefficient indicating the penalty speed for early unlock. Must be between 0 and 50, inclusive. Default 10 - means linear speed. """ assert msg.sender == self.admin_early_unlock, '!admin' # dev: admin_early_unlock only assert _penalty_k <= 50, '!k' assert block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN, 'early' # to avoid frontrun self.prev_penalty_k = self.penalty_k self.penalty_k = _penalty_k self.penalty_upd_ts = block.timestamp log PenaltySpeed(_penalty_k) @external def set_penalty_treasury(_penalty_treasury: address): """ @notice Sets penalty treasury address @dev Only the admin_early_unlock can execute this function. @param _penalty_treasury The address to collect early penalty (default admin address) """ assert msg.sender == self.admin_early_unlock, '!admin' # dev: admin_early_unlock only assert _penalty_treasury != empty(address), '!zero' self.penalty_treasury = _penalty_treasury log PenaltyTreasury(_penalty_treasury) @external def set_all_unlock(): """ @notice Deactivates VotingEscrow and allows users to unlock their locks before lock-end. New deposits will no longer be accepted. @dev Only the admin_unlock_all can execute this function. Make sure there are no rewards for distribution in other contracts. """ assert msg.sender == self.admin_unlock_all, '!admin' # dev: admin_unlock_all only self.all_unlock = True log TotalUnlock(True) @external @view def get_last_user_slope(addr: address) -> int128: """ @notice Get the most recently recorded rate of voting power decrease for `addr` @param addr Address of the user wallet @return Value of the slope """ uepoch: uint256 = self.user_point_epoch[addr] return self.user_point_history[addr][uepoch].slope @external @view def user_point_history__ts(_addr: address, _idx: uint256) -> uint256: """ @notice Get the timestamp for checkpoint `_idx` for `_addr` @param _addr User wallet address @param _idx User epoch number @return Epoch time of the checkpoint """ return self.user_point_history[_addr][_idx].ts @external @view def locked__end(_addr: address) -> uint256: """ @notice Get timestamp when `_addr`'s lock finishes @param _addr User wallet @return Epoch time of the lock end """ return self.locked[_addr].end @internal def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance): """ @notice Record global and per-user data to checkpoint @param addr User's wallet address. No user checkpoint if 0x0 @param old_locked Pevious locked amount / end lock time for the user @param new_locked New locked amount / end lock time for the user """ u_old: Point = empty(Point) u_new: Point = empty(Point) old_dslope: int128 = 0 new_dslope: int128 = 0 _epoch: uint256 = self.epoch if addr != empty(address): # Calculate slopes and biases # Kept at zero when they have to if old_locked.end > block.timestamp and old_locked.amount > 0: u_old.slope = old_locked.amount / convert(self.MAXTIME, int128) u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128) if new_locked.end > block.timestamp and new_locked.amount > 0: u_new.slope = new_locked.amount / convert(self.MAXTIME, int128) u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128) # Read values of scheduled changes in the slope # old_locked.end can be in the past and in the future # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros old_dslope = self.slope_changes[old_locked.end] if new_locked.end != 0: if new_locked.end == old_locked.end: new_dslope = old_dslope else: new_dslope = self.slope_changes[new_locked.end] last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number}) if _epoch > 0: last_point = self.point_history[_epoch] last_checkpoint: uint256 = last_point.ts # initial_last_point is used for extrapolation to calculate block number # (approximately, for *At methods) and save them # as we cannot figure that out exactly from inside the contract initial_last_point: Point = last_point block_slope: uint256 = 0 # dblock/dt if block.timestamp > last_point.ts: block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts) # If last point is already recorded in this block, slope=0 # But that's ok b/c we know the block in such case # Go over weeks to fill history and calculate what the current point is t_i: uint256 = (last_checkpoint / WEEK) * WEEK for i in range(255): # Hopefully it won't happen that this won't get used in 5 years! # If it does, users will be able to withdraw but vote weight will be broken t_i += WEEK d_slope: int128 = 0 if t_i > block.timestamp: t_i = block.timestamp else: d_slope = self.slope_changes[t_i] last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128) last_point.slope += d_slope if last_point.bias < 0: # This can happen last_point.bias = 0 if last_point.slope < 0: # This cannot happen - just in case last_point.slope = 0 last_checkpoint = t_i last_point.ts = t_i last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER _epoch += 1 if t_i == block.timestamp: last_point.blk = block.number break else: self.point_history[_epoch] = last_point self.epoch = _epoch # Now point_history is filled until t=now if addr != empty(address): # If last point was in this block, the slope change has been applied already # But in such case we have 0 slope(s) last_point.slope += (u_new.slope - u_old.slope) last_point.bias += (u_new.bias - u_old.bias) if last_point.slope < 0: last_point.slope = 0 if last_point.bias < 0: last_point.bias = 0 # Record the changed point into history self.point_history[_epoch] = last_point if addr != empty(address): # Schedule the slope changes (slope is going down) # We subtract new_user_slope from [new_locked.end] # and add old_user_slope to [old_locked.end] if old_locked.end > block.timestamp: # old_dslope was <something> - u_old.slope, so we cancel that old_dslope += u_old.slope if new_locked.end == old_locked.end: old_dslope -= u_new.slope # It was a new deposit, not extension self.slope_changes[old_locked.end] = old_dslope if new_locked.end > block.timestamp: if new_locked.end > old_locked.end: new_dslope -= u_new.slope # old slope disappeared at this point self.slope_changes[new_locked.end] = new_dslope # else: we recorded it already in old_dslope # Now handle user history user_epoch: uint256 = self.user_point_epoch[addr] + 1 self.user_point_epoch[addr] = user_epoch u_new.ts = block.timestamp u_new.blk = block.number self.user_point_history[addr][user_epoch] = u_new @internal def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128): """ @notice Deposit and lock tokens for a user @param _addr User's wallet address @param _value Amount to deposit @param unlock_time New time when to unlock the tokens, or 0 if unchanged @param locked_balance Previous locked amount / timestamp """ # block all new deposits (and extensions) in case of unlocked contract assert (not self.all_unlock), "all unlocked,no sense" _locked: LockedBalance = locked_balance supply_before: uint256 = self.supply self.supply = supply_before + _value old_locked: LockedBalance = _locked # Adding to existing lock, or if a lock is expired - creating a new one _locked.amount += convert(_value, int128) if unlock_time != 0: _locked.end = unlock_time self.locked[_addr] = _locked # Possibilities: # Both old_locked.end could be current or expired (>/< block.timestamp) # value == 0 (extend lock) or value > 0 (add to lock or extend lock) # _locked.end > block.timestamp (always) self._checkpoint(_addr, old_locked, _locked) if _value != 0: assert ERC20(self.TOKEN).transferFrom(_addr, self, _value, default_return_value=True) log Deposit(_addr, _value, _locked.end, type, block.timestamp) log Supply(supply_before, supply_before + _value) @external def checkpoint(): """ @notice Record global data to checkpoint """ self._checkpoint(empty(address), empty(LockedBalance), empty(LockedBalance)) @external @nonreentrant("lock") def deposit_for(_addr: address, _value: uint256): """ @notice Deposit `_value` tokens for `_addr` and add to the lock @dev Anyone (even a smart contract) can deposit for someone else, but cannot extend their locktime and deposit for a brand new user @param _addr User's wallet address @param _value Amount to add to user's lock """ _locked: LockedBalance = self.locked[_addr] assert _value > 0 # dev: need non-zero value assert _locked.amount > 0, "No existing lock found" assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE) @external @nonreentrant("lock") def create_lock(_value: uint256, _unlock_time: uint256): """ @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time` @param _value Amount to deposit @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks """ self.assert_not_contract(msg.sender) unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks _locked: LockedBalance = self.locked[msg.sender] assert _value > 0 # dev: need non-zero value assert _locked.amount == 0, "Withdraw old tokens first" assert (unlock_time > block.timestamp), "Can only lock until time in the future" assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long" self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE) @external @nonreentrant("lock") def increase_amount(_value: uint256): """ @notice Deposit `_value` additional tokens for `msg.sender` without modifying the unlock time @param _value Amount of tokens to deposit and add to the lock """ self.assert_not_contract(msg.sender) _locked: LockedBalance = self.locked[msg.sender] assert _value > 0 # dev: need non-zero value assert _locked.amount > 0, "No existing lock found" assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT) @external @nonreentrant("lock") def increase_unlock_time(_unlock_time: uint256): """ @notice Extend the unlock time for `msg.sender` to `_unlock_time` @param _unlock_time New epoch time for unlocking """ self.assert_not_contract(msg.sender) _locked: LockedBalance = self.locked[msg.sender] unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks assert _locked.end > block.timestamp, "Lock expired" assert _locked.amount > 0, "Nothing is locked" assert unlock_time > _locked.end, "Can only increase lock duration" assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long" self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME) @external @nonreentrant("lock") def withdraw(): """ @notice Withdraw all tokens for `msg.sender` @dev Only possible if the lock has expired """ _locked: LockedBalance = self.locked[msg.sender] assert block.timestamp >= _locked.end or self.all_unlock, "lock !expire or !unlock" value: uint256 = convert(_locked.amount, uint256) old_locked: LockedBalance = _locked _locked.end = 0 _locked.amount = 0 self.locked[msg.sender] = _locked supply_before: uint256 = self.supply self.supply = supply_before - value # old_locked can have either expired <= timestamp or zero end # _locked has only 0 end # Both can have >= 0 amount self._checkpoint(msg.sender, old_locked, _locked) assert ERC20(self.TOKEN).transfer(msg.sender, value, default_return_value=True) log Withdraw(msg.sender, value, block.timestamp) log Supply(supply_before, supply_before - value) @external @nonreentrant("lock") def withdraw_early(): """ @notice Withdraws locked tokens for `msg.sender` before lock-end with penalty @dev Only possible if `early_unlock` is enabled (true) By defualt there is linear formula for calculating penalty. In some cases an admin can configure penalty speed using `set_early_unlock_penalty_speed()` L - lock amount k - penalty coefficient, defined by admin (default 1) Tleft - left time to unlock Tmax - MAXLOCK time Penalty amount = L * k * (Tlast / Tmax) """ assert(self.early_unlock == True), "!early unlock" _locked: LockedBalance = self.locked[msg.sender] assert block.timestamp < _locked.end, "lock expired" value: uint256 = convert(_locked.amount, uint256) time_left: uint256 = _locked.end - block.timestamp # to avoid front-run with penalty_k penalty_k_: uint256 = 0 if block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN: penalty_k_ = self.penalty_k else: penalty_k_ = self.prev_penalty_k penalty_ratio: uint256 = (time_left * MULTIPLIER / self.MAXTIME) * penalty_k_ penalty: uint256 = (value * penalty_ratio / MULTIPLIER) / PENALTY_MULTIPLIER if penalty > value: penalty = value user_amount: uint256 = value - penalty old_locked: LockedBalance = _locked _locked.end = 0 _locked.amount = 0 self.locked[msg.sender] = _locked supply_before: uint256 = self.supply self.supply = supply_before - value # old_locked can have either expired <= timestamp or zero end # _locked has only 0 end # Both can have >= 0 amount self._checkpoint(msg.sender, old_locked, _locked) if penalty > 0: assert ERC20(self.TOKEN).transfer(self.penalty_treasury, penalty, default_return_value=True) if user_amount > 0: assert ERC20(self.TOKEN).transfer(msg.sender, user_amount, default_return_value=True) log Withdraw(msg.sender, value, block.timestamp) log Supply(supply_before, supply_before - value) log WithdrawEarly(msg.sender, penalty, time_left) # The following ERC20/minime-compatible methods are not real balanceOf and supply! # They measure the weights for the purpose of voting, so they don't represent # real coins. @internal @view def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find epoch containing block number @param _block Block to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _block """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.point_history[_mid].blk <= _block: _min = _mid else: _max = _mid - 1 return _min @internal @view def find_timestamp_epoch(_timestamp: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find epoch for timestamp @param _timestamp timestamp to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _timestamp """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.point_history[_mid].ts <= _timestamp: _min = _mid else: _max = _mid - 1 return _min @internal @view def find_block_user_epoch(_addr: address, _block: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find epoch for block number @param _addr User for which to find user epoch for @param _block Block to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _block """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.user_point_history[_addr][_mid].blk <= _block: _min = _mid else: _max = _mid - 1 return _min @internal @view def find_timestamp_user_epoch(_addr: address, _timestamp: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find user epoch for timestamp @param _addr User for which to find user epoch for @param _timestamp timestamp to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _timestamp """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.user_point_history[_addr][_mid].ts <= _timestamp: _min = _mid else: _max = _mid - 1 return _min @external @view def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256: """ @notice Get the current voting power for `msg.sender` @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility @param addr User wallet address @param _t Epoch time to return voting power at @return User voting power """ _epoch: uint256 = 0 if _t == block.timestamp: # No need to do binary search, will always live in current epoch _epoch = self.user_point_epoch[addr] else: _epoch = self.find_timestamp_user_epoch(addr, _t, self.user_point_epoch[addr]) if _epoch == 0: return 0 else: last_point: Point = self.user_point_history[addr][_epoch] last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128) if last_point.bias < 0: last_point.bias = 0 return convert(last_point.bias, uint256) @external @view def balanceOfAt(addr: address, _block: uint256) -> uint256: """ @notice Measure voting power of `addr` at block height `_block` @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime @param addr User's wallet address @param _block Block to calculate the voting power at @return Voting power """ # Copying and pasting totalSupply code because Vyper cannot pass by # reference yet assert _block <= block.number _user_epoch: uint256 = self.find_block_user_epoch(addr, _block, self.user_point_epoch[addr]) upoint: Point = self.user_point_history[addr][_user_epoch] max_epoch: uint256 = self.epoch _epoch: uint256 = self.find_block_epoch(_block, max_epoch) point_0: Point = self.point_history[_epoch] d_block: uint256 = 0 d_t: uint256 = 0 if _epoch < max_epoch: point_1: Point = self.point_history[_epoch + 1] d_block = point_1.blk - point_0.blk d_t = point_1.ts - point_0.ts else: d_block = block.number - point_0.blk d_t = block.timestamp - point_0.ts block_time: uint256 = point_0.ts if d_block != 0: block_time += d_t * (_block - point_0.blk) / d_block upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128) if upoint.bias >= 0: return convert(upoint.bias, uint256) else: return 0 @internal @view def supply_at(point: Point, t: uint256) -> uint256: """ @notice Calculate total voting power at some point in the past @param point The point (bias/slope) to start search from @param t Time to calculate the total voting power at @return Total voting power at that time """ last_point: Point = point t_i: uint256 = (last_point.ts / WEEK) * WEEK for i in range(255): t_i += WEEK d_slope: int128 = 0 if t_i > t: t_i = t else: d_slope = self.slope_changes[t_i] last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128) if t_i == t: break last_point.slope += d_slope last_point.ts = t_i if last_point.bias < 0: last_point.bias = 0 return convert(last_point.bias, uint256) @external @view def totalSupply(t: uint256 = block.timestamp) -> uint256: """ @notice Calculate total voting power @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility @return Total voting power """ _epoch: uint256 = 0 if t == block.timestamp: # No need to do binary search, will always live in current epoch _epoch = self.epoch else: _epoch = self.find_timestamp_epoch(t, self.epoch) if _epoch == 0: return 0 else: last_point: Point = self.point_history[_epoch] return self.supply_at(last_point, t) @external @view def totalSupplyAt(_block: uint256) -> uint256: """ @notice Calculate total voting power at some point in the past @param _block Block to calculate the total voting power at @return Total voting power at `_block` """ assert _block <= block.number _epoch: uint256 = self.epoch target_epoch: uint256 = self.find_block_epoch(_block, _epoch) point: Point = self.point_history[target_epoch] dt: uint256 = 0 if target_epoch < _epoch: point_next: Point = self.point_history[target_epoch + 1] if point.blk != point_next.blk: dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk) else: if point.blk != block.number: dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk) # Now dt contains info on how far are we beyond point return self.supply_at(point, point.ts + dt) @external @nonreentrant("lock") def claimExternalRewards(): """ @notice Claims BAL rewards @dev Only possible if the TOKEN is Guage contract """ BalancerMinter(self.balMinter).mint(self.TOKEN) balBalance: uint256 = ERC20(self.balToken).balanceOf(self) if balBalance > 0: # distributes rewards using rewardDistributor into current week if self.rewardReceiver == self.rewardDistributor: assert ERC20(self.balToken).approve(self.rewardDistributor, balBalance, default_return_value=True) RewardDistributor(self.rewardDistributor).depositToken(self.balToken, balBalance) else: assert ERC20(self.balToken).transfer(self.rewardReceiver, balBalance, default_return_value=True) @external def changeRewardReceiver(newReceiver: address): """ @notice Changes the reward receiver address @param newReceiver New address to set as the reward receiver """ assert msg.sender == self.admin, '!admin' assert (self.rewardReceiverChangeable), '!available' assert newReceiver != empty(address), '!empty' self.rewardReceiver = newReceiver log RewardReceiver(newReceiver)
File 5 of 7: Voting Escrow
# @version 0.3.7 """ @title Voting Escrow @author Curve Finance @license MIT @notice Votes have a weight depending on time, so that users are committed to the future of (whatever they are voting for) @dev Vote weight decays linearly over time. Lock time cannot be more than `MAXTIME` (set by creator). """ # Voting escrow to have time-weighted votes # Votes have a weight depending on time, so that users are committed # to the future of (whatever they are voting for). # The weight in this implementation is linear, and lock cannot be more than maxtime: # w ^ # 1 + / # | / # | / # | / # |/ # 0 +--------+------> time # maxtime struct Point: bias: int128 slope: int128 # - dweight / dt ts: uint256 blk: uint256 # block # We cannot really do block numbers per se b/c slope is per time, not per block # and per block could be fairly bad b/c Ethereum changes blocktimes. # What we can do is to extrapolate ***At functions struct LockedBalance: amount: int128 end: uint256 interface ERC20: def decimals() -> uint256: view def name() -> String[64]: view def symbol() -> String[32]: view def balanceOf(account: address) -> uint256: view def transfer(to: address, amount: uint256) -> bool: nonpayable def approve(spender: address, amount: uint256) -> bool: nonpayable def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable # Interface for checking whether address belongs to a whitelisted # type of a smart wallet. # When new types are added - the whole contract is changed # The check() method is modifying to be able to use caching # for individual wallet addresses interface SmartWalletChecker: def check(addr: address) -> bool: nonpayable interface BalancerMinter: def mint(gauge: address) -> uint256: nonpayable interface RewardDistributor: def depositToken(token: address, amount: uint256): nonpayable DEPOSIT_FOR_TYPE: constant(int128) = 0 CREATE_LOCK_TYPE: constant(int128) = 1 INCREASE_LOCK_AMOUNT: constant(int128) = 2 INCREASE_UNLOCK_TIME: constant(int128) = 3 event CommitOwnership: admin: address event ApplyOwnership: admin: address event EarlyUnlock: status: bool event PenaltySpeed: penalty_k: uint256 event PenaltyTreasury: penalty_treasury: address event TotalUnlock: status: bool event RewardReceiver: newReceiver: address event Deposit: provider: indexed(address) value: uint256 locktime: indexed(uint256) type: int128 ts: uint256 event Withdraw: provider: indexed(address) value: uint256 ts: uint256 event WithdrawEarly: provider: indexed(address) penalty: uint256 time_left: uint256 event Supply: prevSupply: uint256 supply: uint256 WEEK: constant(uint256) = 7 * 86400 # all future times are rounded by week MAXTIME: public(uint256) MULTIPLIER: constant(uint256) = 10**18 TOKEN: public(address) NAME: String[64] SYMBOL: String[32] DECIMALS: uint256 supply: public(uint256) locked: public(HashMap[address, LockedBalance]) epoch: public(uint256) point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch] user_point_epoch: public(HashMap[address, uint256]) slope_changes: public(HashMap[uint256, int128]) # time -> signed slope change # Checker for whitelisted (smart contract) wallets which are allowed to deposit # The goal is to prevent tokenizing the escrow future_smart_wallet_checker: public(address) smart_wallet_checker: public(address) admin: public(address) # unlock admins can be set only once. Zero-address means unlock is disabled admin_unlock_all: public(address) admin_early_unlock: public(address) future_admin: public(address) is_initialized: public(bool) early_unlock: public(bool) penalty_k: public(uint256) prev_penalty_k: public(uint256) penalty_upd_ts: public(uint256) PENALTY_COOLDOWN: constant(uint256) = 60 # cooldown to prevent font-run on penalty change PENALTY_MULTIPLIER: constant(uint256) = 10 penalty_treasury: public(address) balMinter: public(address) balToken: public(address) rewardReceiver: public(address) rewardReceiverChangeable: public(bool) rewardDistributor: public(address) all_unlock: public(bool) @external def initialize( _token_addr: address, _name: String[64], _symbol: String[32], _admin_addr: address, _admin_unlock_all: address, _admin_early_unlock: address, _max_time: uint256, _balToken: address, _balMinter: address, _rewardReceiver: address, _rewardReceiverChangeable: bool, _rewardDistributor: address ): """ @notice Contract constructor @param _token_addr 80/20 Token-WETH BPT token address @param _name Token name @param _symbol Token symbol @param _admin_addr Contract admin address @param _admin_unlock_all Admin to enable Unlock-All feature (zero-address to disable forever) @param _admin_early_unlock Admin to enable Eraly-Unlock feature (zero-address to disable forever) @param _max_time Locking max time @param _balToken Address of the Balancer token @param _balMinter Address of the Balancer minter @param _rewardReceiver Address of the reward receiver @param _rewardReceiverChangeable Boolean indicating whether the reward receiver is changeable @param _rewardDistributor The RewardDistributor contract address """ assert(not self.is_initialized), 'only once' self.is_initialized = True assert(_admin_addr != empty(address)), '!empty' self.admin = _admin_addr self.penalty_k = 10 self.prev_penalty_k = 10 self.penalty_upd_ts = block.timestamp self.penalty_treasury = _admin_addr self.TOKEN = _token_addr self.point_history[0].blk = block.number self.point_history[0].ts = block.timestamp _decimals: uint256 = ERC20(_token_addr).decimals() # also validates token for non-zero assert (_decimals >= 6 and _decimals <= 255), '!decimals' self.NAME = _name self.SYMBOL = _symbol self.DECIMALS = _decimals assert(_max_time >= WEEK and _max_time <= WEEK * 52 * 5), '!maxlock' self.MAXTIME = _max_time self.admin_unlock_all = _admin_unlock_all self.admin_early_unlock = _admin_early_unlock self.balToken = _balToken self.balMinter = _balMinter self.rewardReceiver = _rewardReceiver self.rewardReceiverChangeable = _rewardReceiverChangeable self.rewardDistributor = _rewardDistributor @external @view def token() -> address: return self.TOKEN @external @view def name() -> String[64]: return self.NAME @external @view def symbol() -> String[32]: return self.SYMBOL @external @view def decimals() -> uint256: return self.DECIMALS @external def commit_transfer_ownership(addr: address): """ @notice Transfer ownership of VotingEscrow contract to `addr` @param addr Address to have ownership transferred to """ assert msg.sender == self.admin # dev: admin only self.future_admin = addr log CommitOwnership(addr) @external def apply_transfer_ownership(): """ @notice Apply ownership transfer """ assert msg.sender == self.admin # dev: admin only _admin: address = self.future_admin assert _admin != empty(address) # dev: admin not set self.admin = _admin log ApplyOwnership(_admin) @external def commit_smart_wallet_checker(addr: address): """ @notice Set an external contract to check for approved smart contract wallets @param addr Address of Smart contract checker """ assert msg.sender == self.admin self.future_smart_wallet_checker = addr @external def apply_smart_wallet_checker(): """ @notice Apply setting external contract to check approved smart contract wallets """ assert msg.sender == self.admin self.smart_wallet_checker = self.future_smart_wallet_checker @internal def assert_not_contract(addr: address): """ @notice Check if the call is from a whitelisted smart contract, revert if not @param addr Address to be checked """ if addr != tx.origin: checker: address = self.smart_wallet_checker if checker != empty(address): if SmartWalletChecker(checker).check(addr): return raise "Smart contract depositors not allowed" @external def set_early_unlock(_early_unlock: bool): """ @notice Sets the availability for users to unlock their locks before lock-end with penalty @dev Only the admin_early_unlock can execute this function. @param _early_unlock A boolean indicating whether early unlock is allowed or not. """ assert msg.sender == self.admin_early_unlock, '!admin' # dev: admin_early_unlock only assert _early_unlock != self.early_unlock, 'already' self.early_unlock = _early_unlock log EarlyUnlock(_early_unlock) @external def set_early_unlock_penalty_speed(_penalty_k: uint256): """ @notice Sets penalty speed for early unlocking @dev Only the admin can execute this function. To prevent frontrunning we use PENALTY_COOLDOWN period @param _penalty_k Coefficient indicating the penalty speed for early unlock. Must be between 0 and 50, inclusive. Default 10 - means linear speed. """ assert msg.sender == self.admin_early_unlock, '!admin' # dev: admin_early_unlock only assert _penalty_k <= 50, '!k' assert block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN, 'early' # to avoid frontrun self.prev_penalty_k = self.penalty_k self.penalty_k = _penalty_k self.penalty_upd_ts = block.timestamp log PenaltySpeed(_penalty_k) @external def set_penalty_treasury(_penalty_treasury: address): """ @notice Sets penalty treasury address @dev Only the admin_early_unlock can execute this function. @param _penalty_treasury The address to collect early penalty (default admin address) """ assert msg.sender == self.admin_early_unlock, '!admin' # dev: admin_early_unlock only assert _penalty_treasury != empty(address), '!zero' self.penalty_treasury = _penalty_treasury log PenaltyTreasury(_penalty_treasury) @external def set_all_unlock(): """ @notice Deactivates VotingEscrow and allows users to unlock their locks before lock-end. New deposits will no longer be accepted. @dev Only the admin_unlock_all can execute this function. Make sure there are no rewards for distribution in other contracts. """ assert msg.sender == self.admin_unlock_all, '!admin' # dev: admin_unlock_all only self.all_unlock = True log TotalUnlock(True) @external @view def get_last_user_slope(addr: address) -> int128: """ @notice Get the most recently recorded rate of voting power decrease for `addr` @param addr Address of the user wallet @return Value of the slope """ uepoch: uint256 = self.user_point_epoch[addr] return self.user_point_history[addr][uepoch].slope @external @view def user_point_history__ts(_addr: address, _idx: uint256) -> uint256: """ @notice Get the timestamp for checkpoint `_idx` for `_addr` @param _addr User wallet address @param _idx User epoch number @return Epoch time of the checkpoint """ return self.user_point_history[_addr][_idx].ts @external @view def locked__end(_addr: address) -> uint256: """ @notice Get timestamp when `_addr`'s lock finishes @param _addr User wallet @return Epoch time of the lock end """ return self.locked[_addr].end @internal def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance): """ @notice Record global and per-user data to checkpoint @param addr User's wallet address. No user checkpoint if 0x0 @param old_locked Pevious locked amount / end lock time for the user @param new_locked New locked amount / end lock time for the user """ u_old: Point = empty(Point) u_new: Point = empty(Point) old_dslope: int128 = 0 new_dslope: int128 = 0 _epoch: uint256 = self.epoch if addr != empty(address): # Calculate slopes and biases # Kept at zero when they have to if old_locked.end > block.timestamp and old_locked.amount > 0: u_old.slope = old_locked.amount / convert(self.MAXTIME, int128) u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128) if new_locked.end > block.timestamp and new_locked.amount > 0: u_new.slope = new_locked.amount / convert(self.MAXTIME, int128) u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128) # Read values of scheduled changes in the slope # old_locked.end can be in the past and in the future # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros old_dslope = self.slope_changes[old_locked.end] if new_locked.end != 0: if new_locked.end == old_locked.end: new_dslope = old_dslope else: new_dslope = self.slope_changes[new_locked.end] last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number}) if _epoch > 0: last_point = self.point_history[_epoch] last_checkpoint: uint256 = last_point.ts # initial_last_point is used for extrapolation to calculate block number # (approximately, for *At methods) and save them # as we cannot figure that out exactly from inside the contract initial_last_point: Point = last_point block_slope: uint256 = 0 # dblock/dt if block.timestamp > last_point.ts: block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts) # If last point is already recorded in this block, slope=0 # But that's ok b/c we know the block in such case # Go over weeks to fill history and calculate what the current point is t_i: uint256 = (last_checkpoint / WEEK) * WEEK for i in range(255): # Hopefully it won't happen that this won't get used in 5 years! # If it does, users will be able to withdraw but vote weight will be broken t_i += WEEK d_slope: int128 = 0 if t_i > block.timestamp: t_i = block.timestamp else: d_slope = self.slope_changes[t_i] last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128) last_point.slope += d_slope if last_point.bias < 0: # This can happen last_point.bias = 0 if last_point.slope < 0: # This cannot happen - just in case last_point.slope = 0 last_checkpoint = t_i last_point.ts = t_i last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER _epoch += 1 if t_i == block.timestamp: last_point.blk = block.number break else: self.point_history[_epoch] = last_point self.epoch = _epoch # Now point_history is filled until t=now if addr != empty(address): # If last point was in this block, the slope change has been applied already # But in such case we have 0 slope(s) last_point.slope += (u_new.slope - u_old.slope) last_point.bias += (u_new.bias - u_old.bias) if last_point.slope < 0: last_point.slope = 0 if last_point.bias < 0: last_point.bias = 0 # Record the changed point into history self.point_history[_epoch] = last_point if addr != empty(address): # Schedule the slope changes (slope is going down) # We subtract new_user_slope from [new_locked.end] # and add old_user_slope to [old_locked.end] if old_locked.end > block.timestamp: # old_dslope was <something> - u_old.slope, so we cancel that old_dslope += u_old.slope if new_locked.end == old_locked.end: old_dslope -= u_new.slope # It was a new deposit, not extension self.slope_changes[old_locked.end] = old_dslope if new_locked.end > block.timestamp: if new_locked.end > old_locked.end: new_dslope -= u_new.slope # old slope disappeared at this point self.slope_changes[new_locked.end] = new_dslope # else: we recorded it already in old_dslope # Now handle user history user_epoch: uint256 = self.user_point_epoch[addr] + 1 self.user_point_epoch[addr] = user_epoch u_new.ts = block.timestamp u_new.blk = block.number self.user_point_history[addr][user_epoch] = u_new @internal def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128): """ @notice Deposit and lock tokens for a user @param _addr User's wallet address @param _value Amount to deposit @param unlock_time New time when to unlock the tokens, or 0 if unchanged @param locked_balance Previous locked amount / timestamp """ # block all new deposits (and extensions) in case of unlocked contract assert (not self.all_unlock), "all unlocked,no sense" _locked: LockedBalance = locked_balance supply_before: uint256 = self.supply self.supply = supply_before + _value old_locked: LockedBalance = _locked # Adding to existing lock, or if a lock is expired - creating a new one _locked.amount += convert(_value, int128) if unlock_time != 0: _locked.end = unlock_time self.locked[_addr] = _locked # Possibilities: # Both old_locked.end could be current or expired (>/< block.timestamp) # value == 0 (extend lock) or value > 0 (add to lock or extend lock) # _locked.end > block.timestamp (always) self._checkpoint(_addr, old_locked, _locked) if _value != 0: assert ERC20(self.TOKEN).transferFrom(_addr, self, _value, default_return_value=True) log Deposit(_addr, _value, _locked.end, type, block.timestamp) log Supply(supply_before, supply_before + _value) @external def checkpoint(): """ @notice Record global data to checkpoint """ self._checkpoint(empty(address), empty(LockedBalance), empty(LockedBalance)) @external @nonreentrant("lock") def deposit_for(_addr: address, _value: uint256): """ @notice Deposit `_value` tokens for `_addr` and add to the lock @dev Anyone (even a smart contract) can deposit for someone else, but cannot extend their locktime and deposit for a brand new user @param _addr User's wallet address @param _value Amount to add to user's lock """ _locked: LockedBalance = self.locked[_addr] assert _value > 0 # dev: need non-zero value assert _locked.amount > 0, "No existing lock found" assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE) @external @nonreentrant("lock") def create_lock(_value: uint256, _unlock_time: uint256): """ @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time` @param _value Amount to deposit @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks """ self.assert_not_contract(msg.sender) unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks _locked: LockedBalance = self.locked[msg.sender] assert _value > 0 # dev: need non-zero value assert _locked.amount == 0, "Withdraw old tokens first" assert (unlock_time > block.timestamp), "Can only lock until time in the future" assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long" self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE) @external @nonreentrant("lock") def increase_amount(_value: uint256): """ @notice Deposit `_value` additional tokens for `msg.sender` without modifying the unlock time @param _value Amount of tokens to deposit and add to the lock """ self.assert_not_contract(msg.sender) _locked: LockedBalance = self.locked[msg.sender] assert _value > 0 # dev: need non-zero value assert _locked.amount > 0, "No existing lock found" assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT) @external @nonreentrant("lock") def increase_unlock_time(_unlock_time: uint256): """ @notice Extend the unlock time for `msg.sender` to `_unlock_time` @param _unlock_time New epoch time for unlocking """ self.assert_not_contract(msg.sender) _locked: LockedBalance = self.locked[msg.sender] unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks assert _locked.end > block.timestamp, "Lock expired" assert _locked.amount > 0, "Nothing is locked" assert unlock_time > _locked.end, "Can only increase lock duration" assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long" self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME) @external @nonreentrant("lock") def withdraw(): """ @notice Withdraw all tokens for `msg.sender` @dev Only possible if the lock has expired """ _locked: LockedBalance = self.locked[msg.sender] assert block.timestamp >= _locked.end or self.all_unlock, "lock !expire or !unlock" value: uint256 = convert(_locked.amount, uint256) old_locked: LockedBalance = _locked _locked.end = 0 _locked.amount = 0 self.locked[msg.sender] = _locked supply_before: uint256 = self.supply self.supply = supply_before - value # old_locked can have either expired <= timestamp or zero end # _locked has only 0 end # Both can have >= 0 amount self._checkpoint(msg.sender, old_locked, _locked) assert ERC20(self.TOKEN).transfer(msg.sender, value, default_return_value=True) log Withdraw(msg.sender, value, block.timestamp) log Supply(supply_before, supply_before - value) @external @nonreentrant("lock") def withdraw_early(): """ @notice Withdraws locked tokens for `msg.sender` before lock-end with penalty @dev Only possible if `early_unlock` is enabled (true) By defualt there is linear formula for calculating penalty. In some cases an admin can configure penalty speed using `set_early_unlock_penalty_speed()` L - lock amount k - penalty coefficient, defined by admin (default 1) Tleft - left time to unlock Tmax - MAXLOCK time Penalty amount = L * k * (Tlast / Tmax) """ assert(self.early_unlock == True), "!early unlock" _locked: LockedBalance = self.locked[msg.sender] assert block.timestamp < _locked.end, "lock expired" value: uint256 = convert(_locked.amount, uint256) time_left: uint256 = _locked.end - block.timestamp # to avoid front-run with penalty_k penalty_k_: uint256 = 0 if block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN: penalty_k_ = self.penalty_k else: penalty_k_ = self.prev_penalty_k penalty_ratio: uint256 = (time_left * MULTIPLIER / self.MAXTIME) * penalty_k_ penalty: uint256 = (value * penalty_ratio / MULTIPLIER) / PENALTY_MULTIPLIER if penalty > value: penalty = value user_amount: uint256 = value - penalty old_locked: LockedBalance = _locked _locked.end = 0 _locked.amount = 0 self.locked[msg.sender] = _locked supply_before: uint256 = self.supply self.supply = supply_before - value # old_locked can have either expired <= timestamp or zero end # _locked has only 0 end # Both can have >= 0 amount self._checkpoint(msg.sender, old_locked, _locked) if penalty > 0: assert ERC20(self.TOKEN).transfer(self.penalty_treasury, penalty, default_return_value=True) if user_amount > 0: assert ERC20(self.TOKEN).transfer(msg.sender, user_amount, default_return_value=True) log Withdraw(msg.sender, value, block.timestamp) log Supply(supply_before, supply_before - value) log WithdrawEarly(msg.sender, penalty, time_left) # The following ERC20/minime-compatible methods are not real balanceOf and supply! # They measure the weights for the purpose of voting, so they don't represent # real coins. @internal @view def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find epoch containing block number @param _block Block to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _block """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.point_history[_mid].blk <= _block: _min = _mid else: _max = _mid - 1 return _min @internal @view def find_timestamp_epoch(_timestamp: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find epoch for timestamp @param _timestamp timestamp to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _timestamp """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.point_history[_mid].ts <= _timestamp: _min = _mid else: _max = _mid - 1 return _min @internal @view def find_block_user_epoch(_addr: address, _block: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find epoch for block number @param _addr User for which to find user epoch for @param _block Block to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _block """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.user_point_history[_addr][_mid].blk <= _block: _min = _mid else: _max = _mid - 1 return _min @internal @view def find_timestamp_user_epoch(_addr: address, _timestamp: uint256, max_epoch: uint256) -> uint256: """ @notice Binary search to find user epoch for timestamp @param _addr User for which to find user epoch for @param _timestamp timestamp to find @param max_epoch Don't go beyond this epoch @return Epoch which contains _timestamp """ # Binary search _min: uint256 = 0 _max: uint256 = max_epoch for i in range(128): # Will be always enough for 128-bit numbers if _min >= _max: break _mid: uint256 = (_min + _max + 1) / 2 if self.user_point_history[_addr][_mid].ts <= _timestamp: _min = _mid else: _max = _mid - 1 return _min @external @view def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256: """ @notice Get the current voting power for `msg.sender` @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility @param addr User wallet address @param _t Epoch time to return voting power at @return User voting power """ _epoch: uint256 = 0 if _t == block.timestamp: # No need to do binary search, will always live in current epoch _epoch = self.user_point_epoch[addr] else: _epoch = self.find_timestamp_user_epoch(addr, _t, self.user_point_epoch[addr]) if _epoch == 0: return 0 else: last_point: Point = self.user_point_history[addr][_epoch] last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128) if last_point.bias < 0: last_point.bias = 0 return convert(last_point.bias, uint256) @external @view def balanceOfAt(addr: address, _block: uint256) -> uint256: """ @notice Measure voting power of `addr` at block height `_block` @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime @param addr User's wallet address @param _block Block to calculate the voting power at @return Voting power """ # Copying and pasting totalSupply code because Vyper cannot pass by # reference yet assert _block <= block.number _user_epoch: uint256 = self.find_block_user_epoch(addr, _block, self.user_point_epoch[addr]) upoint: Point = self.user_point_history[addr][_user_epoch] max_epoch: uint256 = self.epoch _epoch: uint256 = self.find_block_epoch(_block, max_epoch) point_0: Point = self.point_history[_epoch] d_block: uint256 = 0 d_t: uint256 = 0 if _epoch < max_epoch: point_1: Point = self.point_history[_epoch + 1] d_block = point_1.blk - point_0.blk d_t = point_1.ts - point_0.ts else: d_block = block.number - point_0.blk d_t = block.timestamp - point_0.ts block_time: uint256 = point_0.ts if d_block != 0: block_time += d_t * (_block - point_0.blk) / d_block upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128) if upoint.bias >= 0: return convert(upoint.bias, uint256) else: return 0 @internal @view def supply_at(point: Point, t: uint256) -> uint256: """ @notice Calculate total voting power at some point in the past @param point The point (bias/slope) to start search from @param t Time to calculate the total voting power at @return Total voting power at that time """ last_point: Point = point t_i: uint256 = (last_point.ts / WEEK) * WEEK for i in range(255): t_i += WEEK d_slope: int128 = 0 if t_i > t: t_i = t else: d_slope = self.slope_changes[t_i] last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128) if t_i == t: break last_point.slope += d_slope last_point.ts = t_i if last_point.bias < 0: last_point.bias = 0 return convert(last_point.bias, uint256) @external @view def totalSupply(t: uint256 = block.timestamp) -> uint256: """ @notice Calculate total voting power @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility @return Total voting power """ _epoch: uint256 = 0 if t == block.timestamp: # No need to do binary search, will always live in current epoch _epoch = self.epoch else: _epoch = self.find_timestamp_epoch(t, self.epoch) if _epoch == 0: return 0 else: last_point: Point = self.point_history[_epoch] return self.supply_at(last_point, t) @external @view def totalSupplyAt(_block: uint256) -> uint256: """ @notice Calculate total voting power at some point in the past @param _block Block to calculate the total voting power at @return Total voting power at `_block` """ assert _block <= block.number _epoch: uint256 = self.epoch target_epoch: uint256 = self.find_block_epoch(_block, _epoch) point: Point = self.point_history[target_epoch] dt: uint256 = 0 if target_epoch < _epoch: point_next: Point = self.point_history[target_epoch + 1] if point.blk != point_next.blk: dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk) else: if point.blk != block.number: dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk) # Now dt contains info on how far are we beyond point return self.supply_at(point, point.ts + dt) @external @nonreentrant("lock") def claimExternalRewards(): """ @notice Claims BAL rewards @dev Only possible if the TOKEN is Guage contract """ BalancerMinter(self.balMinter).mint(self.TOKEN) balBalance: uint256 = ERC20(self.balToken).balanceOf(self) if balBalance > 0: # distributes rewards using rewardDistributor into current week if self.rewardReceiver == self.rewardDistributor: assert ERC20(self.balToken).approve(self.rewardDistributor, balBalance, default_return_value=True) RewardDistributor(self.rewardDistributor).depositToken(self.balToken, balBalance) else: assert ERC20(self.balToken).transfer(self.rewardReceiver, balBalance, default_return_value=True) @external def changeRewardReceiver(newReceiver: address): """ @notice Changes the reward receiver address @param newReceiver New address to set as the reward receiver """ assert msg.sender == self.admin, '!admin' assert (self.rewardReceiverChangeable), '!available' assert newReceiver != empty(address), '!empty' self.rewardReceiver = newReceiver log RewardReceiver(newReceiver)
File 6 of 7: RewardFaucet
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == _ENTERED; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 amount) external returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; import "../extensions/IERC20Permit.sol"; import "../../../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 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)); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); } /** * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ 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"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); _callOptionalReturn(token, approvalCall); } } /** * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. * Revert on invalid signature. */ 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"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token)); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ 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"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ 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"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ 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); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ 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); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ 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); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ 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 { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } // SPDX-License-Identifier: MIT // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.8.18; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; interface IRewardDistributor { function claimToken(address user, address token) external returns (uint256); function faucetDepositToken(address token, uint256 amount) external; } /** * @title RewardFaucet * @notice The contract offers users the flexibility to manage and distribute rewards while * ensuring equitable and even distribution of tokens over specified time periods. */ contract RewardFaucet is ReentrancyGuard { using SafeERC20 for IERC20; bool public isInitialized; IRewardDistributor public rewardDistributor; mapping(address token => uint256 rewardAmount) public totalTokenRewards; mapping(address token => mapping(uint256 weekStart => uint256 amount)) public tokenWeekAmounts; event WeeksDistributions(address token, uint256 totalAmount, uint256 weeksCount); event ExactWeekDistribution(address token, uint256 totalAmount, uint256 weeksCount); event DistributePast(address token, uint256 amount, uint256 weekStart); event MovePastRewards(address token, uint256 moveAmount, uint256 pastWeekStart, uint256 nextWeekStart); function initialize(address _rewardDistributor) external { require(!isInitialized, "!twice"); require(_rewardDistributor != address(0), '!zero'); isInitialized = true; rewardDistributor = IRewardDistributor(_rewardDistributor); } /** * @notice Deposit rewards evenly across a specified period starting from the current week * @dev weekAmount = amount / weeksCount * @param token The address of the token to be deposited as a reward * @param amount The total amount of `token` to be deposited as a reward over the entire period * @param weeksCount The number of weeks, including the current one, over which the rewards will be distributed */ function depositEqualWeeksPeriod( address token, uint256 amount, uint256 weeksCount ) nonReentrant external { require(weeksCount > 0 && weeksCount <= 104, '!week'); // if some tokens were transferred directly uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token]; uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount; uint256 weekAmount = totalAmount / weeksCount; if (weeksCount != 1) { // current week will be distributed now, thus filling map from the next week uint256 weekStart = _roundUpTimestamp(block.timestamp); for (uint256 i = 2; i <= weeksCount; ) { // last iteration with leftovers if (i == weeksCount) { tokenWeekAmounts[token][weekStart] += (totalAmount - weekAmount * (weeksCount - 1)); break; } tokenWeekAmounts[token][weekStart] += weekAmount; unchecked { i++; } weekStart += 1 weeks; } // first week will be distributed now, thus subtract 1 weekAmount totalTokenRewards[token] += totalAmount - weekAmount; } // don't confuse with 'totalAmount' IERC20(token).safeTransferFrom(msg.sender, address(this), amount); // current week distribution IRewardDistributor rewardDistributor_ = rewardDistributor; IERC20(token).forceApprove(address(rewardDistributor_), weekAmount); rewardDistributor_.faucetDepositToken(token, weekAmount); emit WeeksDistributions(token, totalAmount, weeksCount); } /** * @notice Deposit rewards into a specific week (starting from current) * @dev If a week is separated from previous reward weeks, * or rewards were not claimed in previous weeks in the RewardDistributor contract, * users may need to manually call the `distributePastRewards()` function * to ensure that the rewards are added to the RewardDistributor contract. * @param token The address of the token to be deposited as a reward * @param amount The amount of `token` to be deposited as a reward * @param weekTimeStamp The timestamp of the week for which rewards are being distributed */ function depositExactWeek( address token, uint256 amount, uint256 weekTimeStamp ) nonReentrant external { require( weekTimeStamp >= _roundDownTimestamp(block.timestamp) && weekTimeStamp <= block.timestamp + 104 weeks, 'bad week' ); // if some tokens were transferred directly uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token]; uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount; IERC20(token).safeTransferFrom(msg.sender, address(this), amount); uint256 weekStart = _roundDownTimestamp(weekTimeStamp); if (weekStart == _roundDownTimestamp(block.timestamp)) { // current week will be distributed now IRewardDistributor rewardDistributor_ = rewardDistributor; IERC20(token).forceApprove(address(rewardDistributor_), totalAmount); rewardDistributor_.faucetDepositToken(token, totalAmount); } else { tokenWeekAmounts[token][weekStart] += totalAmount; totalTokenRewards[token] += totalAmount; } emit ExactWeekDistribution(token, totalAmount, weekStart); } /** * @notice Collects all rewards for 10 past weeks and sends them to RewardDistributor * @param token - the token address to collect rewards */ function distributePastRewards(address token) external { if (totalTokenRewards[token] == 0) return; uint256 weekStart = _roundDownTimestamp(block.timestamp); uint256 totalAmount; for (uint256 i = 0; i < 10; ++i) { uint256 amount = tokenWeekAmounts[token][weekStart]; if (amount == 0) { weekStart -= 1 weeks; continue; } tokenWeekAmounts[token][weekStart] = 0; totalAmount += amount; weekStart -= 1 weeks; } if (totalAmount > 0) { totalTokenRewards[token] -= totalAmount; IRewardDistributor rewardDistributor_ = rewardDistributor; IERC20(token).forceApprove(address(rewardDistributor_), totalAmount); rewardDistributor_.faucetDepositToken(token, totalAmount); emit DistributePast(token, totalAmount, weekStart); } } /** * @notice Manually moves unclaimed past rewards to the next week to enable distribution * @dev This function can be called by anyone * @param token The reward token address to be moved * @param pastWeekTimestamp The timestamp representing a point in the past week (must be at least 10 weeks ago) */ function movePastRewards(address token, uint256 pastWeekTimestamp) external { uint256 pastWeekStart = _roundDownTimestamp(pastWeekTimestamp); require(pastWeekStart < _roundDownTimestamp(block.timestamp) - 9 weeks, '!outdate'); uint256 nextWeekStart = _roundUpTimestamp(block.timestamp); uint256 moveAmount = tokenWeekAmounts[token][pastWeekStart]; tokenWeekAmounts[token][pastWeekStart] = 0; tokenWeekAmounts[token][nextWeekStart] += moveAmount; emit MovePastRewards(token, moveAmount, pastWeekStart, nextWeekStart); } /** * @notice Returns the reward amount for a specified week (represented by a point within the week) * @dev The `pointOfWeek` parameter is any timestamp within the week: wStart[---p-----]wEnd * @param token The address of the reward token to be distributed * @param pointOfWeek The timestamp representing a specific week * @return The reward amount for the specified week */ function getTokenWeekAmounts(address token, uint256 pointOfWeek) external view returns (uint256) { uint256 weekStart = _roundDownTimestamp(pointOfWeek); return tokenWeekAmounts[token][weekStart]; } /** * @notice Returns rewards for a specified number of weeks starting from the current week * @param token The address of the reward token to be distributed * @param weeksCount The number of weeks to check rewards for * @return An array containing reward amounts for each week */ function getUpcomingRewardsForNWeeks( address token, uint256 weeksCount ) external view returns (uint256[] memory) { uint256 weekStart = _roundDownTimestamp(block.timestamp); uint256[] memory rewards = new uint256[](weeksCount); for (uint256 i = 0; i < weeksCount; i++) { rewards[i] = tokenWeekAmounts[token][weekStart + i * 1 weeks]; } return rewards; } /** * @dev Rounds the provided timestamp down to the beginning of the previous week (Thurs 00:00 UTC) */ function _roundDownTimestamp( uint256 timestamp ) private pure returns (uint256) { return (timestamp / 1 weeks) * 1 weeks; } /** * @dev Rounds the provided timestamp up to the beginning of the next week (Thurs 00:00 UTC) */ function _roundUpTimestamp( uint256 timestamp ) private pure returns (uint256) { // Overflows are impossible here for all realistic inputs. return _roundDownTimestamp(timestamp + 1 weeks); } }
File 7 of 7: RewardFaucet
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == _ENTERED; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 amount) external returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; import "../extensions/IERC20Permit.sol"; import "../../../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 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)); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); } /** * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ 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"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); _callOptionalReturn(token, approvalCall); } } /** * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. * Revert on invalid signature. */ 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"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token)); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ 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"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ 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"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ 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); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ 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); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ 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); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ 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 { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } // SPDX-License-Identifier: MIT // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. pragma solidity ^0.8.18; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; interface IRewardDistributor { function claimToken(address user, address token) external returns (uint256); function faucetDepositToken(address token, uint256 amount) external; } /** * @title RewardFaucet * @notice The contract offers users the flexibility to manage and distribute rewards while * ensuring equitable and even distribution of tokens over specified time periods. */ contract RewardFaucet is ReentrancyGuard { using SafeERC20 for IERC20; bool public isInitialized; IRewardDistributor public rewardDistributor; mapping(address token => uint256 rewardAmount) public totalTokenRewards; mapping(address token => mapping(uint256 weekStart => uint256 amount)) public tokenWeekAmounts; event WeeksDistributions(address token, uint256 totalAmount, uint256 weeksCount); event ExactWeekDistribution(address token, uint256 totalAmount, uint256 weeksCount); event DistributePast(address token, uint256 amount, uint256 weekStart); event MovePastRewards(address token, uint256 moveAmount, uint256 pastWeekStart, uint256 nextWeekStart); function initialize(address _rewardDistributor) external { require(!isInitialized, "!twice"); require(_rewardDistributor != address(0), '!zero'); isInitialized = true; rewardDistributor = IRewardDistributor(_rewardDistributor); } /** * @notice Deposit rewards evenly across a specified period starting from the current week * @dev weekAmount = amount / weeksCount * @param token The address of the token to be deposited as a reward * @param amount The total amount of `token` to be deposited as a reward over the entire period * @param weeksCount The number of weeks, including the current one, over which the rewards will be distributed */ function depositEqualWeeksPeriod( address token, uint256 amount, uint256 weeksCount ) nonReentrant external { require(weeksCount > 0 && weeksCount <= 104, '!week'); // if some tokens were transferred directly uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token]; uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount; uint256 weekAmount = totalAmount / weeksCount; if (weeksCount != 1) { // current week will be distributed now, thus filling map from the next week uint256 weekStart = _roundUpTimestamp(block.timestamp); for (uint256 i = 2; i <= weeksCount; ) { // last iteration with leftovers if (i == weeksCount) { tokenWeekAmounts[token][weekStart] += (totalAmount - weekAmount * (weeksCount - 1)); break; } tokenWeekAmounts[token][weekStart] += weekAmount; unchecked { i++; } weekStart += 1 weeks; } // first week will be distributed now, thus subtract 1 weekAmount totalTokenRewards[token] += totalAmount - weekAmount; } // don't confuse with 'totalAmount' IERC20(token).safeTransferFrom(msg.sender, address(this), amount); // current week distribution IRewardDistributor rewardDistributor_ = rewardDistributor; IERC20(token).forceApprove(address(rewardDistributor_), weekAmount); rewardDistributor_.faucetDepositToken(token, weekAmount); emit WeeksDistributions(token, totalAmount, weeksCount); } /** * @notice Deposit rewards into a specific week (starting from current) * @dev If a week is separated from previous reward weeks, * or rewards were not claimed in previous weeks in the RewardDistributor contract, * users may need to manually call the `distributePastRewards()` function * to ensure that the rewards are added to the RewardDistributor contract. * @param token The address of the token to be deposited as a reward * @param amount The amount of `token` to be deposited as a reward * @param weekTimeStamp The timestamp of the week for which rewards are being distributed */ function depositExactWeek( address token, uint256 amount, uint256 weekTimeStamp ) nonReentrant external { require( weekTimeStamp >= _roundDownTimestamp(block.timestamp) && weekTimeStamp <= block.timestamp + 104 weeks, 'bad week' ); // if some tokens were transferred directly uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token]; uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount; IERC20(token).safeTransferFrom(msg.sender, address(this), amount); uint256 weekStart = _roundDownTimestamp(weekTimeStamp); if (weekStart == _roundDownTimestamp(block.timestamp)) { // current week will be distributed now IRewardDistributor rewardDistributor_ = rewardDistributor; IERC20(token).forceApprove(address(rewardDistributor_), totalAmount); rewardDistributor_.faucetDepositToken(token, totalAmount); } else { tokenWeekAmounts[token][weekStart] += totalAmount; totalTokenRewards[token] += totalAmount; } emit ExactWeekDistribution(token, totalAmount, weekStart); } /** * @notice Collects all rewards for 10 past weeks and sends them to RewardDistributor * @param token - the token address to collect rewards */ function distributePastRewards(address token) external { if (totalTokenRewards[token] == 0) return; uint256 weekStart = _roundDownTimestamp(block.timestamp); uint256 totalAmount; for (uint256 i = 0; i < 10; ++i) { uint256 amount = tokenWeekAmounts[token][weekStart]; if (amount == 0) { weekStart -= 1 weeks; continue; } tokenWeekAmounts[token][weekStart] = 0; totalAmount += amount; weekStart -= 1 weeks; } if (totalAmount > 0) { totalTokenRewards[token] -= totalAmount; IRewardDistributor rewardDistributor_ = rewardDistributor; IERC20(token).forceApprove(address(rewardDistributor_), totalAmount); rewardDistributor_.faucetDepositToken(token, totalAmount); emit DistributePast(token, totalAmount, weekStart); } } /** * @notice Manually moves unclaimed past rewards to the next week to enable distribution * @dev This function can be called by anyone * @param token The reward token address to be moved * @param pastWeekTimestamp The timestamp representing a point in the past week (must be at least 10 weeks ago) */ function movePastRewards(address token, uint256 pastWeekTimestamp) external { uint256 pastWeekStart = _roundDownTimestamp(pastWeekTimestamp); require(pastWeekStart < _roundDownTimestamp(block.timestamp) - 9 weeks, '!outdate'); uint256 nextWeekStart = _roundUpTimestamp(block.timestamp); uint256 moveAmount = tokenWeekAmounts[token][pastWeekStart]; tokenWeekAmounts[token][pastWeekStart] = 0; tokenWeekAmounts[token][nextWeekStart] += moveAmount; emit MovePastRewards(token, moveAmount, pastWeekStart, nextWeekStart); } /** * @notice Returns the reward amount for a specified week (represented by a point within the week) * @dev The `pointOfWeek` parameter is any timestamp within the week: wStart[---p-----]wEnd * @param token The address of the reward token to be distributed * @param pointOfWeek The timestamp representing a specific week * @return The reward amount for the specified week */ function getTokenWeekAmounts(address token, uint256 pointOfWeek) external view returns (uint256) { uint256 weekStart = _roundDownTimestamp(pointOfWeek); return tokenWeekAmounts[token][weekStart]; } /** * @notice Returns rewards for a specified number of weeks starting from the current week * @param token The address of the reward token to be distributed * @param weeksCount The number of weeks to check rewards for * @return An array containing reward amounts for each week */ function getUpcomingRewardsForNWeeks( address token, uint256 weeksCount ) external view returns (uint256[] memory) { uint256 weekStart = _roundDownTimestamp(block.timestamp); uint256[] memory rewards = new uint256[](weeksCount); for (uint256 i = 0; i < weeksCount; i++) { rewards[i] = tokenWeekAmounts[token][weekStart + i * 1 weeks]; } return rewards; } /** * @dev Rounds the provided timestamp down to the beginning of the previous week (Thurs 00:00 UTC) */ function _roundDownTimestamp( uint256 timestamp ) private pure returns (uint256) { return (timestamp / 1 weeks) * 1 weeks; } /** * @dev Rounds the provided timestamp up to the beginning of the next week (Thurs 00:00 UTC) */ function _roundUpTimestamp( uint256 timestamp ) private pure returns (uint256) { // Overflows are impossible here for all realistic inputs. return _roundDownTimestamp(timestamp + 1 weeks); } }