Transaction Hash:
Block:
18432076 at Oct-26-2023 04:41:47 AM +UTC
Transaction Fee:
0.00295323934016097 ETH
$7.48
Gas Used:
134,505 Gas / 21.956353594 Gwei
Emitted Events:
170 |
ERC1967Proxy.0x33e6f269701b611439c5bd9eae485d1b2f10d29b632a6f0d5688c93c2d77af1f( 0x33e6f269701b611439c5bd9eae485d1b2f10d29b632a6f0d5688c93c2d77af1f, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 00000000000000000000000000000000000000000000000000000777c0a63b79, 000000000000000000000000000000000000000000000000000000006539ee0b )
|
171 |
ERC1967Proxy.0x31eeb6a0d26c29b4c243d704ff4ae6feebcc2e8b123df7ea0bd12083c3083cb8( 0x31eeb6a0d26c29b4c243d704ff4ae6feebcc2e8b123df7ea0bd12083c3083cb8, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 000000000000000000000000000000000000000000000000000001c69229e343, 0000000000000000000000000000000000000000000000000000000000000000 )
|
172 |
IlluviumERC20.Minted( _by=ERC1967Proxy, _to=[Sender] 0x5398bf1c06652a8d69795397dff5c3bcb4822708, _value=18709031959012747264 )
|
173 |
IlluviumERC20.Transferred( _by=ERC1967Proxy, _from=0x0000000000000000000000000000000000000000, _to=[Sender] 0x5398bf1c06652a8d69795397dff5c3bcb4822708, _value=18709031959012747264 )
|
174 |
IlluviumERC20.Transfer( _from=0x0000000000000000000000000000000000000000, _to=[Sender] 0x5398bf1c06652a8d69795397dff5c3bcb4822708, _value=18709031959012747264 )
|
175 |
ERC1967Proxy.0x30d438cf38db3f29630029343ab01e801276913697f489030c0613ddc4aadaaf( 0x30d438cf38db3f29630029343ab01e801276913697f489030c0613ddc4aadaaf, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 00000000000000000000000000000000000000000000000103a3d5712862a800, 0000000000000000000000000000000000000000000000000000000000000001 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x5398BF1C...cB4822708 |
2.270227935878031342 Eth
Nonce: 128
|
2.267274696537870372 Eth
Nonce: 129
| 0.00295323934016097 | ||
0x767FE9ED...959D7ca0E | |||||
0x7f5f854F...e2B34291D | (Illuvium: Migrate) | ||||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 15.78199359746399361 Eth | 15.78200704796399361 Eth | 0.0000134505 |
Execution Trace
ERC1967Proxy.1bfe80b8( )
ILVPool.unstakeMultiple( _stakes=, _unstakingYield=True )
ERC1967Proxy.STATICCALL( )
-
PoolFactory.DELEGATECALL( )
-
ERC1967Proxy.STATICCALL( )
-
PoolFactory.DELEGATECALL( )
-
ERC1967Proxy.STATICCALL( )
-
PoolFactory.DELEGATECALL( )
-
ERC1967Proxy.STATICCALL( )
-
PoolFactory.DELEGATECALL( )
-
ERC1967Proxy.c59e2aca( )
PoolFactory.mintYieldTo( _to=0x5398BF1C06652a8d69795397dff5C3BcB4822708, _value=18709031959012747264, _useSILV=False )
-
IlluviumERC20.mint( _to=0x5398BF1C06652a8d69795397dff5C3BcB4822708, _value=18709031959012747264 )
-
unstakeMultiple[CorePool (ln:1744)]
_requireNotPaused[CorePool (ln:1746)]
verifyState[CorePool (ln:1984)]
paused[CorePool (ln:1984)]
verifyNonZeroInput[CorePool (ln:1750)]
_useV1Weight[CorePool (ln:1754)]
getDeposit[CorePool (ln:1954)]
_updateReward[CorePool (ln:1755)]
_sync[CorePool (ln:1998)]
shouldUpdateRatio[CorePool (ln:1824)]
updateILVPerSecond[CorePool (ln:1825)]
endTime[CorePool (ln:1829)]
_now256[CorePool (ln:1833)]
toUint64[CorePool (ln:1838)]
_now256[CorePool (ln:1838)]
_now256[CorePool (ln:1842)]
_now256[CorePool (ln:1842)]
ilvPerSecond[CorePool (ln:1844)]
totalWeight[CorePool (ln:1846)]
getRewardPerWeight[CorePool (ln:1848)]
toUint64[CorePool (ln:1849)]
LogSync[CorePool (ln:1851)]
earned[CorePool (ln:2004)]
earned[CorePool (ln:2006)]
toUint128[CorePool (ln:2008)]
toUint128[CorePool (ln:2010)]
LogUpdateRewards[CorePool (ln:2015)]
verifyState[CorePool (ln:1766)]
_now256[CorePool (ln:1766)]
verifyNonZeroInput[CorePool (ln:1768)]
verifyState[CorePool (ln:1775)]
verifyState[CorePool (ln:1777)]
weight[CorePool (ln:1779)]
toUint120[CorePool (ln:1788)]
weight[CorePool (ln:1790)]
toUint248[CorePool (ln:1798)]
mintYieldTo[CorePool (ln:1806)]
safeTransfer[CorePool (ln:1809)]
LogUnstakeLockedMultiple[CorePool (ln:1812)]
File 1 of 5: ERC1967Proxy
File 2 of 5: IlluviumERC20
File 3 of 5: ILVPool
File 4 of 5: ERC1967Proxy
File 5 of 5: PoolFactory
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to * be specified by overriding the virtual {_implementation} function. * * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a * different contract through the {_delegate} function. * * The success and return data of the delegated call will be returned back to the caller of the proxy. */ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * * This function does not return to its internall call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { // solhint-disable-next-line no-inline-assembly assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /** * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internall call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); _delegate(_implementation()); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other * function in the contract matches the call data. */ fallback () external payable virtual { _fallback(); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * is empty. */ receive () external payable virtual { _fallback(); } /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. * * If overriden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual { } } /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. * * _Available since v4.1._ * */ abstract contract ERC1967Upgrade { // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Returns the current implementation address. */ function _getImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * * Emits an {Upgraded} event. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Perform implementation upgrade with additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } } /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal { address oldImplementation = _getImplementation(); // Initial upgrade and setup call _setImplementation(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } // Perform rollback test if not already in progress StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT); if (!rollbackTesting.value) { // Trigger rollback using upgradeTo from the new implementation rollbackTesting.value = true; Address.functionDelegateCall( newImplementation, abi.encodeWithSignature( "upgradeTo(address)", oldImplementation ) ); rollbackTesting.value = false; // Check rollback was effective require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); // Finally reset to the new implementation and log the upgrade _setImplementation(newImplementation); emit Upgraded(newImplementation); } } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * * Emits a {BeaconUpgraded} event. */ function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { require(newAdmin != address(0), "ERC1967: new admin is the zero address"); StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. */ function _changeAdmin(address newAdmin) internal { emit AdminChanged(_getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Emitted when the beacon is upgraded. */ event BeaconUpgraded(address indexed beacon); /** * @dev Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlot.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require( Address.isContract(newBeacon), "ERC1967: new beacon is not a contract" ); require( Address.isContract(IBeacon(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; } } /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeacon { /** * @dev Must return an address that can be used as a delegate call target. * * {BeaconProxy} will check that this address is a contract. */ function implementation() external view returns (address); } /** * @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; } /** * @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, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (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 functionCall(target, data, "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"); require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: value }(data); return _verifyCallResult(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) { require(isContract(target), "Address: static call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.staticcall(data); return _verifyCallResult(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) { require(isContract(target), "Address: delegate call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return _verifyCallResult(success, returndata, errorMessage); } function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { if (success) { return returndata; } else { // 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(errorMessage); } } } } /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { assembly { r.slot := slot } } } /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } /** * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. */ contract ProxyAdmin is Ownable { /** * @dev Returns the current implementation of `proxy`. * * Requirements: * * - This contract must be the admin of `proxy`. */ function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("implementation()")) == 0x5c60da1b (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); require(success); return abi.decode(returndata, (address)); } /** * @dev Returns the current admin of `proxy`. * * Requirements: * * - This contract must be the admin of `proxy`. */ function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("admin()")) == 0xf851a440 (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); require(success); return abi.decode(returndata, (address)); } /** * @dev Changes the admin of `proxy` to `newAdmin`. * * Requirements: * * - This contract must be the current admin of `proxy`. */ function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner { proxy.changeAdmin(newAdmin); } /** * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. * * Requirements: * * - This contract must be the admin of `proxy`. */ function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner { proxy.upgradeTo(implementation); } /** * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See * {TransparentUpgradeableProxy-upgradeToAndCall}. * * Requirements: * * - This contract must be the admin of `proxy`. */ function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner { proxy.upgradeToAndCall{value: msg.value}(implementation, data); } } /** * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify * continuation of the upgradability. * * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism. * * _Available since v4.1._ */ abstract contract UUPSUpgradeable is ERC1967Upgrade { function upgradeTo(address newImplementation) external virtual { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, bytes(""), false); } function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, data, true); } function _authorizeUpgrade(address newImplementation) internal virtual; } abstract contract Proxiable is UUPSUpgradeable { function _authorizeUpgrade(address newImplementation) internal override { _beforeUpgrade(newImplementation); } function _beforeUpgrade(address newImplementation) internal virtual; } contract ChildOfProxiable is Proxiable { function _beforeUpgrade(address newImplementation) internal virtual override {} } /** * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an * implementation address that can be changed. This address is stored in storage in the location specified by * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the * implementation behind the proxy. */ contract ERC1967Proxy is Proxy, ERC1967Upgrade { /** * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded * function call, and allows initializating the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); _upgradeToAndCall(_logic, _data, false); } /** * @dev Returns the current implementation address. */ function _implementation() internal view virtual override returns (address impl) { return ERC1967Upgrade._getImplementation(); } } /** * @dev This contract implements a proxy that is upgradeable by an admin. * * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector * clashing], which can potentially be used in an attack, this contract uses the * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two * things that go hand in hand: * * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if * that call matches one of the admin functions exposed by the proxy itself. * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the * implementation. If the admin tries to call a function on the implementation it will fail with an error that says * "admin cannot fallback to proxy target". * * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due * to sudden errors when trying to call a function from the proxy implementation. * * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. */ contract TransparentUpgradeableProxy is ERC1967Proxy { /** * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); _changeAdmin(admin_); } /** * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. */ modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } /** * @dev Returns the current admin. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ function admin() external ifAdmin returns (address admin_) { admin_ = _getAdmin(); } /** * @dev Returns the current implementation. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ function implementation() external ifAdmin returns (address implementation_) { implementation_ = _implementation(); } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. * * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. */ function changeAdmin(address newAdmin) external virtual ifAdmin { _changeAdmin(newAdmin); } /** * @dev Upgrade the implementation of the proxy. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. */ function upgradeTo(address newImplementation) external ifAdmin { _upgradeToAndCall(newImplementation, bytes(""), false); } /** * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the * proxied contract. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. */ function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { _upgradeToAndCall(newImplementation, data, true); } /** * @dev Returns the current admin. */ function _admin() internal view virtual returns (address) { return _getAdmin(); } /** * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. */ function _beforeFallback() internal virtual override { require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target"); super._beforeFallback(); } } // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins. contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy { constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {} }
File 2 of 5: IlluviumERC20
// SPDX-License-Identifier: MIT pragma solidity 0.8.1; /** * @title ERC20 token receiver interface * * @dev Interface for any contract that wants to support safe transfers * from ERC20 token smart contracts. * @dev Inspired by ERC721 and ERC223 token standards * * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md * @dev See https://github.com/ethereum/EIPs/issues/223 * * @author Basil Gorin */ interface ERC20Receiver { /** * @notice Handle the receipt of a ERC20 token(s) * @dev The ERC20 smart contract calls this function on the recipient * after a successful transfer (`safeTransferFrom`). * This function MAY throw to revert and reject the transfer. * Return of other than the magic value MUST result in the transaction being reverted. * @notice The contract address is always the message sender. * A wallet/broker/auction application MUST implement the wallet interface * if it will accept safe transfers. * @param _operator The address which called `safeTransferFrom` function * @param _from The address which previously owned the token * @param _value amount of tokens which is being transferred * @param _data additional data with no specified format * @return `bytes4(keccak256("onERC20Received(address,address,uint256,bytes)"))` unless throwing */ function onERC20Received(address _operator, address _from, uint256 _value, bytes calldata _data) external returns(bytes4); } // SPDX-License-Identifier: MIT pragma solidity 0.8.1; import "../utils/AddressUtils.sol"; import "../utils/AccessControl.sol"; import "./ERC20Receiver.sol"; /** * @title Illuvium (ILV) ERC20 token * * @notice Illuvium is a core ERC20 token powering the game. * It serves as an in-game currency, is tradable on exchanges, * it powers up the governance protocol (Illuvium DAO) and participates in Yield Farming. * * @dev Token Summary: * - Symbol: ILV * - Name: Illuvium * - Decimals: 18 * - Initial token supply: 7,000,000 ILV * - Maximum final token supply: 10,000,000 ILV * - Up to 3,000,000 ILV may get minted in 3 years period via yield farming * - Mintable: total supply may increase * - Burnable: total supply may decrease * * @dev Token balances and total supply are effectively 192 bits long, meaning that maximum * possible total supply smart contract is able to track is 2^192 (close to 10^40 tokens) * * @dev Smart contract doesn't use safe math. All arithmetic operations are overflow/underflow safe. * Additionally, Solidity 0.8.1 enforces overflow/underflow safety. * * @dev ERC20: reviewed according to https://eips.ethereum.org/EIPS/eip-20 * * @dev ERC20: contract has passed OpenZeppelin ERC20 tests, * see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/token/ERC20/ERC20.behavior.js * see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/token/ERC20/ERC20.test.js * see adopted copies of these tests in the `test` folder * * @dev ERC223/ERC777: not supported; * send tokens via `safeTransferFrom` and implement `ERC20Receiver.onERC20Received` on the receiver instead * * @dev Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) - resolved * Related events and functions are marked with "ISBN:978-1-7281-3027-9" tag: * - event Transferred(address indexed _by, address indexed _from, address indexed _to, uint256 _value) * - event Approved(address indexed _owner, address indexed _spender, uint256 _oldValue, uint256 _value) * - function increaseAllowance(address _spender, uint256 _value) public returns (bool) * - function decreaseAllowance(address _spender, uint256 _value) public returns (bool) * See: https://ieeexplore.ieee.org/document/8802438 * See: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * @author Basil Gorin */ contract IlluviumERC20 is AccessControl { /** * @dev Smart contract unique identifier, a random number * @dev Should be regenerated each time smart contact source code is changed * and changes smart contract itself is to be redeployed * @dev Generated using https://www.random.org/bytes/ */ uint256 public constant TOKEN_UID = 0x83ecb176af7c4f35a45ff0018282e3a05a1018065da866182df12285866f5a2c; /** * @notice Name of the token: Illuvium * * @notice ERC20 name of the token (long name) * * @dev ERC20 `function name() public view returns (string)` * * @dev Field is declared public: getter name() is created when compiled, * it returns the name of the token. */ string public constant name = "Illuvium"; /** * @notice Symbol of the token: ILV * * @notice ERC20 symbol of that token (short name) * * @dev ERC20 `function symbol() public view returns (string)` * * @dev Field is declared public: getter symbol() is created when compiled, * it returns the symbol of the token */ string public constant symbol = "ILV"; /** * @notice Decimals of the token: 18 * * @dev ERC20 `function decimals() public view returns (uint8)` * * @dev Field is declared public: getter decimals() is created when compiled, * it returns the number of decimals used to get its user representation. * For example, if `decimals` equals `6`, a balance of `1,500,000` tokens should * be displayed to a user as `1,5` (`1,500,000 / 10 ** 6`). * * @dev NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including balanceOf() and transfer(). */ uint8 public constant decimals = 18; /** * @notice Total supply of the token: initially 7,000,000, * with the potential to grow up to 10,000,000 during yield farming period (3 years) * * @dev ERC20 `function totalSupply() public view returns (uint256)` * * @dev Field is declared public: getter totalSupply() is created when compiled, * it returns the amount of tokens in existence. */ uint256 public totalSupply; // is set to 7 million * 10^18 in the constructor /** * @dev A record of all the token balances * @dev This mapping keeps record of all token owners: * owner => balance */ mapping(address => uint256) public tokenBalances; /** * @notice A record of each account's voting delegate * * @dev Auxiliary data structure used to sum up an account's voting power * * @dev This mapping keeps record of all voting power delegations: * voting delegator (token owner) => voting delegate */ mapping(address => address) public votingDelegates; /** * @notice A voting power record binds voting power of a delegate to a particular * block when the voting power delegation change happened */ struct VotingPowerRecord { /* * @dev block.number when delegation has changed; starting from * that block voting power value is in effect */ uint64 blockNumber; /* * @dev cumulative voting power a delegate has obtained starting * from the block stored in blockNumber */ uint192 votingPower; } /** * @notice A record of each account's voting power * * @dev Primarily data structure to store voting power for each account. * Voting power sums up from the account's token balance and delegated * balances. * * @dev Stores current value and entire history of its changes. * The changes are stored as an array of checkpoints. * Checkpoint is an auxiliary data structure containing voting * power (number of votes) and block number when the checkpoint is saved * * @dev Maps voting delegate => voting power record */ mapping(address => VotingPowerRecord[]) public votingPowerHistory; /** * @dev A record of nonces for signing/validating signatures in `delegateWithSig` * for every delegate, increases after successful validation * * @dev Maps delegate address => delegate nonce */ mapping(address => uint256) public nonces; /** * @notice A record of all the allowances to spend tokens on behalf * @dev Maps token owner address to an address approved to spend * some tokens on behalf, maps approved address to that amount * @dev owner => spender => value */ mapping(address => mapping(address => uint256)) public transferAllowances; /** * @notice Enables ERC20 transfers of the tokens * (transfer by the token owner himself) * @dev Feature FEATURE_TRANSFERS must be enabled in order for * `transfer()` function to succeed */ uint32 public constant FEATURE_TRANSFERS = 0x0000_0001; /** * @notice Enables ERC20 transfers on behalf * (transfer by someone else on behalf of token owner) * @dev Feature FEATURE_TRANSFERS_ON_BEHALF must be enabled in order for * `transferFrom()` function to succeed * @dev Token owner must call `approve()` first to authorize * the transfer on behalf */ uint32 public constant FEATURE_TRANSFERS_ON_BEHALF = 0x0000_0002; /** * @dev Defines if the default behavior of `transfer` and `transferFrom` * checks if the receiver smart contract supports ERC20 tokens * @dev When feature FEATURE_UNSAFE_TRANSFERS is enabled the transfers do not * check if the receiver smart contract supports ERC20 tokens, * i.e. `transfer` and `transferFrom` behave like `unsafeTransferFrom` * @dev When feature FEATURE_UNSAFE_TRANSFERS is disabled (default) the transfers * check if the receiver smart contract supports ERC20 tokens, * i.e. `transfer` and `transferFrom` behave like `safeTransferFrom` */ uint32 public constant FEATURE_UNSAFE_TRANSFERS = 0x0000_0004; /** * @notice Enables token owners to burn their own tokens, * including locked tokens which are burnt first * @dev Feature FEATURE_OWN_BURNS must be enabled in order for * `burn()` function to succeed when called by token owner */ uint32 public constant FEATURE_OWN_BURNS = 0x0000_0008; /** * @notice Enables approved operators to burn tokens on behalf of their owners, * including locked tokens which are burnt first * @dev Feature FEATURE_OWN_BURNS must be enabled in order for * `burn()` function to succeed when called by approved operator */ uint32 public constant FEATURE_BURNS_ON_BEHALF = 0x0000_0010; /** * @notice Enables delegators to elect delegates * @dev Feature FEATURE_DELEGATIONS must be enabled in order for * `delegate()` function to succeed */ uint32 public constant FEATURE_DELEGATIONS = 0x0000_0020; /** * @notice Enables delegators to elect delegates on behalf * (via an EIP712 signature) * @dev Feature FEATURE_DELEGATIONS must be enabled in order for * `delegateWithSig()` function to succeed */ uint32 public constant FEATURE_DELEGATIONS_ON_BEHALF = 0x0000_0040; /** * @notice Token creator is responsible for creating (minting) * tokens to an arbitrary address * @dev Role ROLE_TOKEN_CREATOR allows minting tokens * (calling `mint` function) */ uint32 public constant ROLE_TOKEN_CREATOR = 0x0001_0000; /** * @notice Token destroyer is responsible for destroying (burning) * tokens owned by an arbitrary address * @dev Role ROLE_TOKEN_DESTROYER allows burning tokens * (calling `burn` function) */ uint32 public constant ROLE_TOKEN_DESTROYER = 0x0002_0000; /** * @notice ERC20 receivers are allowed to receive tokens without ERC20 safety checks, * which may be useful to simplify tokens transfers into "legacy" smart contracts * @dev When `FEATURE_UNSAFE_TRANSFERS` is not enabled addresses having * `ROLE_ERC20_RECEIVER` permission are allowed to receive tokens * via `transfer` and `transferFrom` functions in the same way they * would via `unsafeTransferFrom` function * @dev When `FEATURE_UNSAFE_TRANSFERS` is enabled `ROLE_ERC20_RECEIVER` permission * doesn't affect the transfer behaviour since * `transfer` and `transferFrom` behave like `unsafeTransferFrom` for any receiver * @dev ROLE_ERC20_RECEIVER is a shortening for ROLE_UNSAFE_ERC20_RECEIVER */ uint32 public constant ROLE_ERC20_RECEIVER = 0x0004_0000; /** * @notice ERC20 senders are allowed to send tokens without ERC20 safety checks, * which may be useful to simplify tokens transfers into "legacy" smart contracts * @dev When `FEATURE_UNSAFE_TRANSFERS` is not enabled senders having * `ROLE_ERC20_SENDER` permission are allowed to send tokens * via `transfer` and `transferFrom` functions in the same way they * would via `unsafeTransferFrom` function * @dev When `FEATURE_UNSAFE_TRANSFERS` is enabled `ROLE_ERC20_SENDER` permission * doesn't affect the transfer behaviour since * `transfer` and `transferFrom` behave like `unsafeTransferFrom` for any receiver * @dev ROLE_ERC20_SENDER is a shortening for ROLE_UNSAFE_ERC20_SENDER */ uint32 public constant ROLE_ERC20_SENDER = 0x0008_0000; /** * @dev Magic value to be returned by ERC20Receiver upon successful reception of token(s) * @dev Equal to `bytes4(keccak256("onERC20Received(address,address,uint256,bytes)"))`, * which can be also obtained as `ERC20Receiver(address(0)).onERC20Received.selector` */ bytes4 private constant ERC20_RECEIVED = 0x4fc35859; /** * @notice EIP-712 contract's domain typeHash, see https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash */ bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); /** * @notice EIP-712 delegation struct typeHash, see https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash */ bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegate,uint256 nonce,uint256 expiry)"); /** * @dev Fired in transfer(), transferFrom() and some other (non-ERC20) functions * * @dev ERC20 `event Transfer(address indexed _from, address indexed _to, uint256 _value)` * * @param _from an address tokens were consumed from * @param _to an address tokens were sent to * @param _value number of tokens transferred */ event Transfer(address indexed _from, address indexed _to, uint256 _value); /** * @dev Fired in approve() and approveAtomic() functions * * @dev ERC20 `event Approval(address indexed _owner, address indexed _spender, uint256 _value)` * * @param _owner an address which granted a permission to transfer * tokens on its behalf * @param _spender an address which received a permission to transfer * tokens on behalf of the owner `_owner` * @param _value amount of tokens granted to transfer on behalf */ event Approval(address indexed _owner, address indexed _spender, uint256 _value); /** * @dev Fired in mint() function * * @param _by an address which minted some tokens (transaction sender) * @param _to an address the tokens were minted to * @param _value an amount of tokens minted */ event Minted(address indexed _by, address indexed _to, uint256 _value); /** * @dev Fired in burn() function * * @param _by an address which burned some tokens (transaction sender) * @param _from an address the tokens were burnt from * @param _value an amount of tokens burnt */ event Burnt(address indexed _by, address indexed _from, uint256 _value); /** * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) * * @dev Similar to ERC20 Transfer event, but also logs an address which executed transfer * * @dev Fired in transfer(), transferFrom() and some other (non-ERC20) functions * * @param _by an address which performed the transfer * @param _from an address tokens were consumed from * @param _to an address tokens were sent to * @param _value number of tokens transferred */ event Transferred(address indexed _by, address indexed _from, address indexed _to, uint256 _value); /** * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) * * @dev Similar to ERC20 Approve event, but also logs old approval value * * @dev Fired in approve() and approveAtomic() functions * * @param _owner an address which granted a permission to transfer * tokens on its behalf * @param _spender an address which received a permission to transfer * tokens on behalf of the owner `_owner` * @param _oldValue previously granted amount of tokens to transfer on behalf * @param _value new granted amount of tokens to transfer on behalf */ event Approved(address indexed _owner, address indexed _spender, uint256 _oldValue, uint256 _value); /** * @dev Notifies that a key-value pair in `votingDelegates` mapping has changed, * i.e. a delegator address has changed its delegate address * * @param _of delegator address, a token owner * @param _from old delegate, an address which delegate right is revoked * @param _to new delegate, an address which received the voting power */ event DelegateChanged(address indexed _of, address indexed _from, address indexed _to); /** * @dev Notifies that a key-value pair in `votingPowerHistory` mapping has changed, * i.e. a delegate's voting power has changed. * * @param _of delegate whose voting power has changed * @param _fromVal previous number of votes delegate had * @param _toVal new number of votes delegate has */ event VotingPowerChanged(address indexed _of, uint256 _fromVal, uint256 _toVal); /** * @dev Deploys the token smart contract, * assigns initial token supply to the address specified * * @param _initialHolder owner of the initial token supply */ constructor(address _initialHolder) { // verify initial holder address non-zero (is set) require(_initialHolder != address(0), "_initialHolder not set (zero address)"); // mint initial supply mint(_initialHolder, 7_000_000e18); } // ===== Start: ERC20/ERC223/ERC777 functions ===== /** * @notice Gets the balance of a particular address * * @dev ERC20 `function balanceOf(address _owner) public view returns (uint256 balance)` * * @param _owner the address to query the the balance for * @return balance an amount of tokens owned by the address specified */ function balanceOf(address _owner) public view returns (uint256 balance) { // read the balance and return return tokenBalances[_owner]; } /** * @notice Transfers some tokens to an external address or a smart contract * * @dev ERC20 `function transfer(address _to, uint256 _value) public returns (bool success)` * * @dev Called by token owner (an address which has a * positive token balance tracked by this smart contract) * @dev Throws on any error like * * insufficient token balance or * * incorrect `_to` address: * * zero address or * * self address or * * smart contract which doesn't support ERC20 * * @param _to an address to transfer tokens to, * must be either an external address or a smart contract, * compliant with the ERC20 standard * @param _value amount of tokens to be transferred, must * be greater than zero * @return success true on success, throws otherwise */ function transfer(address _to, uint256 _value) public returns (bool success) { // just delegate call to `transferFrom`, // `FEATURE_TRANSFERS` is verified inside it return transferFrom(msg.sender, _to, _value); } /** * @notice Transfers some tokens on behalf of address `_from' (token owner) * to some other address `_to` * * @dev ERC20 `function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)` * * @dev Called by token owner on his own or approved address, * an address approved earlier by token owner to * transfer some amount of tokens on its behalf * @dev Throws on any error like * * insufficient token balance or * * incorrect `_to` address: * * zero address or * * same as `_from` address (self transfer) * * smart contract which doesn't support ERC20 * * @param _from token owner which approved caller (transaction sender) * to transfer `_value` of tokens on its behalf * @param _to an address to transfer tokens to, * must be either an external address or a smart contract, * compliant with the ERC20 standard * @param _value amount of tokens to be transferred, must * be greater than zero * @return success true on success, throws otherwise */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { // depending on `FEATURE_UNSAFE_TRANSFERS` we execute either safe (default) // or unsafe transfer // if `FEATURE_UNSAFE_TRANSFERS` is enabled // or receiver has `ROLE_ERC20_RECEIVER` permission // or sender has `ROLE_ERC20_SENDER` permission if(isFeatureEnabled(FEATURE_UNSAFE_TRANSFERS) || isOperatorInRole(_to, ROLE_ERC20_RECEIVER) || isSenderInRole(ROLE_ERC20_SENDER)) { // we execute unsafe transfer - delegate call to `unsafeTransferFrom`, // `FEATURE_TRANSFERS` is verified inside it unsafeTransferFrom(_from, _to, _value); } // otherwise - if `FEATURE_UNSAFE_TRANSFERS` is disabled // and receiver doesn't have `ROLE_ERC20_RECEIVER` permission else { // we execute safe transfer - delegate call to `safeTransferFrom`, passing empty `_data`, // `FEATURE_TRANSFERS` is verified inside it safeTransferFrom(_from, _to, _value, ""); } // both `unsafeTransferFrom` and `safeTransferFrom` throw on any error, so // if we're here - it means operation successful, // just return true return true; } /** * @notice Transfers some tokens on behalf of address `_from' (token owner) * to some other address `_to` * * @dev Inspired by ERC721 safeTransferFrom, this function allows to * send arbitrary data to the receiver on successful token transfer * @dev Called by token owner on his own or approved address, * an address approved earlier by token owner to * transfer some amount of tokens on its behalf * @dev Throws on any error like * * insufficient token balance or * * incorrect `_to` address: * * zero address or * * same as `_from` address (self transfer) * * smart contract which doesn't support ERC20Receiver interface * @dev Returns silently on success, throws otherwise * * @param _from token owner which approved caller (transaction sender) * to transfer `_value` of tokens on its behalf * @param _to an address to transfer tokens to, * must be either an external address or a smart contract, * compliant with the ERC20 standard * @param _value amount of tokens to be transferred, must * be greater than zero * @param _data [optional] additional data with no specified format, * sent in onERC20Received call to `_to` in case if its a smart contract */ function safeTransferFrom(address _from, address _to, uint256 _value, bytes memory _data) public { // first delegate call to `unsafeTransferFrom` // to perform the unsafe token(s) transfer unsafeTransferFrom(_from, _to, _value); // after the successful transfer - check if receiver supports // ERC20Receiver and execute a callback handler `onERC20Received`, // reverting whole transaction on any error: // check if receiver `_to` supports ERC20Receiver interface if(AddressUtils.isContract(_to)) { // if `_to` is a contract - execute onERC20Received bytes4 response = ERC20Receiver(_to).onERC20Received(msg.sender, _from, _value, _data); // expected response is ERC20_RECEIVED require(response == ERC20_RECEIVED, "invalid onERC20Received response"); } } /** * @notice Transfers some tokens on behalf of address `_from' (token owner) * to some other address `_to` * * @dev In contrast to `safeTransferFrom` doesn't check recipient * smart contract to support ERC20 tokens (ERC20Receiver) * @dev Designed to be used by developers when the receiver is known * to support ERC20 tokens but doesn't implement ERC20Receiver interface * @dev Called by token owner on his own or approved address, * an address approved earlier by token owner to * transfer some amount of tokens on its behalf * @dev Throws on any error like * * insufficient token balance or * * incorrect `_to` address: * * zero address or * * same as `_from` address (self transfer) * @dev Returns silently on success, throws otherwise * * @param _from token owner which approved caller (transaction sender) * to transfer `_value` of tokens on its behalf * @param _to an address to transfer tokens to, * must be either an external address or a smart contract, * compliant with the ERC20 standard * @param _value amount of tokens to be transferred, must * be greater than zero */ function unsafeTransferFrom(address _from, address _to, uint256 _value) public { // if `_from` is equal to sender, require transfers feature to be enabled // otherwise require transfers on behalf feature to be enabled require(_from == msg.sender && isFeatureEnabled(FEATURE_TRANSFERS) || _from != msg.sender && isFeatureEnabled(FEATURE_TRANSFERS_ON_BEHALF), _from == msg.sender? "transfers are disabled": "transfers on behalf are disabled"); // non-zero source address check - Zeppelin // obviously, zero source address is a client mistake // it's not part of ERC20 standard but it's reasonable to fail fast // since for zero value transfer transaction succeeds otherwise require(_from != address(0), "ERC20: transfer from the zero address"); // Zeppelin msg // non-zero recipient address check require(_to != address(0), "ERC20: transfer to the zero address"); // Zeppelin msg // sender and recipient cannot be the same require(_from != _to, "sender and recipient are the same (_from = _to)"); // sending tokens to the token smart contract itself is a client mistake require(_to != address(this), "invalid recipient (transfer to the token smart contract itself)"); // according to ERC-20 Token Standard, https://eips.ethereum.org/EIPS/eip-20 // "Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event." if(_value == 0) { // emit an ERC20 transfer event emit Transfer(_from, _to, _value); // don't forget to return - we're done return; } // no need to make arithmetic overflow check on the _value - by design of mint() // in case of transfer on behalf if(_from != msg.sender) { // read allowance value - the amount of tokens allowed to transfer - into the stack uint256 _allowance = transferAllowances[_from][msg.sender]; // verify sender has an allowance to transfer amount of tokens requested require(_allowance >= _value, "ERC20: transfer amount exceeds allowance"); // Zeppelin msg // update allowance value on the stack _allowance -= _value; // update the allowance value in storage transferAllowances[_from][msg.sender] = _allowance; // emit an improved atomic approve event emit Approved(_from, msg.sender, _allowance + _value, _allowance); // emit an ERC20 approval event to reflect the decrease emit Approval(_from, msg.sender, _allowance); } // verify sender has enough tokens to transfer on behalf require(tokenBalances[_from] >= _value, "ERC20: transfer amount exceeds balance"); // Zeppelin msg // perform the transfer: // decrease token owner (sender) balance tokenBalances[_from] -= _value; // increase `_to` address (receiver) balance tokenBalances[_to] += _value; // move voting power associated with the tokens transferred __moveVotingPower(votingDelegates[_from], votingDelegates[_to], _value); // emit an improved transfer event emit Transferred(msg.sender, _from, _to, _value); // emit an ERC20 transfer event emit Transfer(_from, _to, _value); } /** * @notice Approves address called `_spender` to transfer some amount * of tokens on behalf of the owner * * @dev ERC20 `function approve(address _spender, uint256 _value) public returns (bool success)` * * @dev Caller must not necessarily own any tokens to grant the permission * * @param _spender an address approved by the caller (token owner) * to spend some tokens on its behalf * @param _value an amount of tokens spender `_spender` is allowed to * transfer on behalf of the token owner * @return success true on success, throws otherwise */ function approve(address _spender, uint256 _value) public returns (bool success) { // non-zero spender address check - Zeppelin // obviously, zero spender address is a client mistake // it's not part of ERC20 standard but it's reasonable to fail fast require(_spender != address(0), "ERC20: approve to the zero address"); // Zeppelin msg // read old approval value to emmit an improved event (ISBN:978-1-7281-3027-9) uint256 _oldValue = transferAllowances[msg.sender][_spender]; // perform an operation: write value requested into the storage transferAllowances[msg.sender][_spender] = _value; // emit an improved atomic approve event (ISBN:978-1-7281-3027-9) emit Approved(msg.sender, _spender, _oldValue, _value); // emit an ERC20 approval event emit Approval(msg.sender, _spender, _value); // operation successful, return true return true; } /** * @notice Returns the amount which _spender is still allowed to withdraw from _owner. * * @dev ERC20 `function allowance(address _owner, address _spender) public view returns (uint256 remaining)` * * @dev A function to check an amount of tokens owner approved * to transfer on its behalf by some other address called "spender" * * @param _owner an address which approves transferring some tokens on its behalf * @param _spender an address approved to transfer some tokens on behalf * @return remaining an amount of tokens approved address `_spender` can transfer on behalf * of token owner `_owner` */ function allowance(address _owner, address _spender) public view returns (uint256 remaining) { // read the value from storage and return return transferAllowances[_owner][_spender]; } // ===== End: ERC20/ERC223/ERC777 functions ===== // ===== Start: Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) ===== /** * @notice Increases the allowance granted to `spender` by the transaction sender * * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) * * @dev Throws if value to increase by is zero or too big and causes arithmetic overflow * * @param _spender an address approved by the caller (token owner) * to spend some tokens on its behalf * @param _value an amount of tokens to increase by * @return success true on success, throws otherwise */ function increaseAllowance(address _spender, uint256 _value) public virtual returns (bool) { // read current allowance value uint256 currentVal = transferAllowances[msg.sender][_spender]; // non-zero _value and arithmetic overflow check on the allowance require(currentVal + _value > currentVal, "zero value approval increase or arithmetic overflow"); // delegate call to `approve` with the new value return approve(_spender, currentVal + _value); } /** * @notice Decreases the allowance granted to `spender` by the caller. * * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) * * @dev Throws if value to decrease by is zero or is bigger than currently allowed value * * @param _spender an address approved by the caller (token owner) * to spend some tokens on its behalf * @param _value an amount of tokens to decrease by * @return success true on success, throws otherwise */ function decreaseAllowance(address _spender, uint256 _value) public virtual returns (bool) { // read current allowance value uint256 currentVal = transferAllowances[msg.sender][_spender]; // non-zero _value check on the allowance require(_value > 0, "zero value approval decrease"); // verify allowance decrease doesn't underflow require(currentVal >= _value, "ERC20: decreased allowance below zero"); // delegate call to `approve` with the new value return approve(_spender, currentVal - _value); } // ===== End: Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) ===== // ===== Start: Minting/burning extension ===== /** * @dev Mints (creates) some tokens to address specified * @dev The value specified is treated as is without taking * into account what `decimals` value is * @dev Behaves effectively as `mintTo` function, allowing * to specify an address to mint tokens to * @dev Requires sender to have `ROLE_TOKEN_CREATOR` permission * * @dev Throws on overflow, if totalSupply + _value doesn't fit into uint256 * * @param _to an address to mint tokens to * @param _value an amount of tokens to mint (create) */ function mint(address _to, uint256 _value) public { // check if caller has sufficient permissions to mint tokens require(isSenderInRole(ROLE_TOKEN_CREATOR), "insufficient privileges (ROLE_TOKEN_CREATOR required)"); // non-zero recipient address check require(_to != address(0), "ERC20: mint to the zero address"); // Zeppelin msg // non-zero _value and arithmetic overflow check on the total supply // this check automatically secures arithmetic overflow on the individual balance require(totalSupply + _value > totalSupply, "zero value mint or arithmetic overflow"); // uint192 overflow check (required by voting delegation) require(totalSupply + _value <= type(uint192).max, "total supply overflow (uint192)"); // perform mint: // increase total amount of tokens value totalSupply += _value; // increase `_to` address balance tokenBalances[_to] += _value; // create voting power associated with the tokens minted __moveVotingPower(address(0), votingDelegates[_to], _value); // fire a minted event emit Minted(msg.sender, _to, _value); // emit an improved transfer event emit Transferred(msg.sender, address(0), _to, _value); // fire ERC20 compliant transfer event emit Transfer(address(0), _to, _value); } /** * @dev Burns (destroys) some tokens from the address specified * @dev The value specified is treated as is without taking * into account what `decimals` value is * @dev Behaves effectively as `burnFrom` function, allowing * to specify an address to burn tokens from * @dev Requires sender to have `ROLE_TOKEN_DESTROYER` permission * * @param _from an address to burn some tokens from * @param _value an amount of tokens to burn (destroy) */ function burn(address _from, uint256 _value) public { // check if caller has sufficient permissions to burn tokens // and if not - check for possibility to burn own tokens or to burn on behalf if(!isSenderInRole(ROLE_TOKEN_DESTROYER)) { // if `_from` is equal to sender, require own burns feature to be enabled // otherwise require burns on behalf feature to be enabled require(_from == msg.sender && isFeatureEnabled(FEATURE_OWN_BURNS) || _from != msg.sender && isFeatureEnabled(FEATURE_BURNS_ON_BEHALF), _from == msg.sender? "burns are disabled": "burns on behalf are disabled"); // in case of burn on behalf if(_from != msg.sender) { // read allowance value - the amount of tokens allowed to be burnt - into the stack uint256 _allowance = transferAllowances[_from][msg.sender]; // verify sender has an allowance to burn amount of tokens requested require(_allowance >= _value, "ERC20: burn amount exceeds allowance"); // Zeppelin msg // update allowance value on the stack _allowance -= _value; // update the allowance value in storage transferAllowances[_from][msg.sender] = _allowance; // emit an improved atomic approve event emit Approved(msg.sender, _from, _allowance + _value, _allowance); // emit an ERC20 approval event to reflect the decrease emit Approval(_from, msg.sender, _allowance); } } // at this point we know that either sender is ROLE_TOKEN_DESTROYER or // we burn own tokens or on behalf (in latest case we already checked and updated allowances) // we have left to execute balance checks and burning logic itself // non-zero burn value check require(_value != 0, "zero value burn"); // non-zero source address check - Zeppelin require(_from != address(0), "ERC20: burn from the zero address"); // Zeppelin msg // verify `_from` address has enough tokens to destroy // (basically this is a arithmetic overflow check) require(tokenBalances[_from] >= _value, "ERC20: burn amount exceeds balance"); // Zeppelin msg // perform burn: // decrease `_from` address balance tokenBalances[_from] -= _value; // decrease total amount of tokens value totalSupply -= _value; // destroy voting power associated with the tokens burnt __moveVotingPower(votingDelegates[_from], address(0), _value); // fire a burnt event emit Burnt(msg.sender, _from, _value); // emit an improved transfer event emit Transferred(msg.sender, _from, address(0), _value); // fire ERC20 compliant transfer event emit Transfer(_from, address(0), _value); } // ===== End: Minting/burning extension ===== // ===== Start: DAO Support (Compound-like voting delegation) ===== /** * @notice Gets current voting power of the account `_of` * @param _of the address of account to get voting power of * @return current cumulative voting power of the account, * sum of token balances of all its voting delegators */ function getVotingPower(address _of) public view returns (uint256) { // get a link to an array of voting power history records for an address specified VotingPowerRecord[] storage history = votingPowerHistory[_of]; // lookup the history and return latest element return history.length == 0? 0: history[history.length - 1].votingPower; } /** * @notice Gets past voting power of the account `_of` at some block `_blockNum` * @dev Throws if `_blockNum` is not in the past (not the finalized block) * @param _of the address of account to get voting power of * @param _blockNum block number to get the voting power at * @return past cumulative voting power of the account, * sum of token balances of all its voting delegators at block number `_blockNum` */ function getVotingPowerAt(address _of, uint256 _blockNum) public view returns (uint256) { // make sure block number is not in the past (not the finalized block) require(_blockNum < block.number, "not yet determined"); // Compound msg // get a link to an array of voting power history records for an address specified VotingPowerRecord[] storage history = votingPowerHistory[_of]; // if voting power history for the account provided is empty if(history.length == 0) { // than voting power is zero - return the result return 0; } // check latest voting power history record block number: // if history was not updated after the block of interest if(history[history.length - 1].blockNumber <= _blockNum) { // we're done - return last voting power record return getVotingPower(_of); } // check first voting power history record block number: // if history was never updated before the block of interest if(history[0].blockNumber > _blockNum) { // we're done - voting power at the block num of interest was zero return 0; } // `votingPowerHistory[_of]` is an array ordered by `blockNumber`, ascending; // apply binary search on `votingPowerHistory[_of]` to find such an entry number `i`, that // `votingPowerHistory[_of][i].blockNumber <= _blockNum`, but in the same time // `votingPowerHistory[_of][i + 1].blockNumber > _blockNum` // return the result - voting power found at index `i` return history[__binaryLookup(_of, _blockNum)].votingPower; } /** * @dev Reads an entire voting power history array for the delegate specified * * @param _of delegate to query voting power history for * @return voting power history array for the delegate of interest */ function getVotingPowerHistory(address _of) public view returns(VotingPowerRecord[] memory) { // return an entire array as memory return votingPowerHistory[_of]; } /** * @dev Returns length of the voting power history array for the delegate specified; * useful since reading an entire array just to get its length is expensive (gas cost) * * @param _of delegate to query voting power history length for * @return voting power history array length for the delegate of interest */ function getVotingPowerHistoryLength(address _of) public view returns(uint256) { // read array length and return return votingPowerHistory[_of].length; } /** * @notice Delegates voting power of the delegator `msg.sender` to the delegate `_to` * * @dev Accepts zero value address to delegate voting power to, effectively * removing the delegate in that case * * @param _to address to delegate voting power to */ function delegate(address _to) public { // verify delegations are enabled require(isFeatureEnabled(FEATURE_DELEGATIONS), "delegations are disabled"); // delegate call to `__delegate` __delegate(msg.sender, _to); } /** * @notice Delegates voting power of the delegator (represented by its signature) to the delegate `_to` * * @dev Accepts zero value address to delegate voting power to, effectively * removing the delegate in that case * * @dev Compliant with EIP-712: Ethereum typed structured data hashing and signing, * see https://eips.ethereum.org/EIPS/eip-712 * * @param _to address to delegate voting power to * @param _nonce nonce used to construct the signature, and used to validate it; * nonce is increased by one after successful signature validation and vote delegation * @param _exp signature expiration time * @param v the recovery byte of the signature * @param r half of the ECDSA signature pair * @param s half of the ECDSA signature pair */ function delegateWithSig(address _to, uint256 _nonce, uint256 _exp, uint8 v, bytes32 r, bytes32 s) public { // verify delegations on behalf are enabled require(isFeatureEnabled(FEATURE_DELEGATIONS_ON_BEHALF), "delegations on behalf are disabled"); // build the EIP-712 contract domain separator bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), block.chainid, address(this))); // build the EIP-712 hashStruct of the delegation message bytes32 hashStruct = keccak256(abi.encode(DELEGATION_TYPEHASH, _to, _nonce, _exp)); // calculate the EIP-712 digest "\\x19\\x01" ‖ domainSeparator ‖ hashStruct(message) bytes32 digest = keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, hashStruct)); // recover the address who signed the message with v, r, s address signer = ecrecover(digest, v, r, s); // perform message integrity and security validations require(signer != address(0), "invalid signature"); // Compound msg require(_nonce == nonces[signer], "invalid nonce"); // Compound msg require(block.timestamp < _exp, "signature expired"); // Compound msg // update the nonce for that particular signer to avoid replay attack nonces[signer]++; // delegate call to `__delegate` - execute the logic required __delegate(signer, _to); } /** * @dev Auxiliary function to delegate delegator's `_from` voting power to the delegate `_to` * @dev Writes to `votingDelegates` and `votingPowerHistory` mappings * * @param _from delegator who delegates his voting power * @param _to delegate who receives the voting power */ function __delegate(address _from, address _to) private { // read current delegate to be replaced by a new one address _fromDelegate = votingDelegates[_from]; // read current voting power (it is equal to token balance) uint256 _value = tokenBalances[_from]; // reassign voting delegate to `_to` votingDelegates[_from] = _to; // update voting power for `_fromDelegate` and `_to` __moveVotingPower(_fromDelegate, _to, _value); // emit an event emit DelegateChanged(_from, _fromDelegate, _to); } /** * @dev Auxiliary function to move voting power `_value` * from delegate `_from` to the delegate `_to` * * @dev Doesn't have any effect if `_from == _to`, or if `_value == 0` * * @param _from delegate to move voting power from * @param _to delegate to move voting power to * @param _value voting power to move from `_from` to `_to` */ function __moveVotingPower(address _from, address _to, uint256 _value) private { // if there is no move (`_from == _to`) or there is nothing to move (`_value == 0`) if(_from == _to || _value == 0) { // return silently with no action return; } // if source address is not zero - decrease its voting power if(_from != address(0)) { // read current source address voting power uint256 _fromVal = getVotingPower(_from); // calculate decreased voting power // underflow is not possible by design: // voting power is limited by token balance which is checked by the callee uint256 _toVal = _fromVal - _value; // update source voting power from `_fromVal` to `_toVal` __updateVotingPower(_from, _fromVal, _toVal); } // if destination address is not zero - increase its voting power if(_to != address(0)) { // read current destination address voting power uint256 _fromVal = getVotingPower(_to); // calculate increased voting power // overflow is not possible by design: // max token supply limits the cumulative voting power uint256 _toVal = _fromVal + _value; // update destination voting power from `_fromVal` to `_toVal` __updateVotingPower(_to, _fromVal, _toVal); } } /** * @dev Auxiliary function to update voting power of the delegate `_of` * from value `_fromVal` to value `_toVal` * * @param _of delegate to update its voting power * @param _fromVal old voting power of the delegate * @param _toVal new voting power of the delegate */ function __updateVotingPower(address _of, uint256 _fromVal, uint256 _toVal) private { // get a link to an array of voting power history records for an address specified VotingPowerRecord[] storage history = votingPowerHistory[_of]; // if there is an existing voting power value stored for current block if(history.length != 0 && history[history.length - 1].blockNumber == block.number) { // update voting power which is already stored in the current block history[history.length - 1].votingPower = uint192(_toVal); } // otherwise - if there is no value stored for current block else { // add new element into array representing the value for current block history.push(VotingPowerRecord(uint64(block.number), uint192(_toVal))); } // emit an event emit VotingPowerChanged(_of, _fromVal, _toVal); } /** * @dev Auxiliary function to lookup an element in a sorted (asc) array of elements * * @dev This function finds the closest element in an array to the value * of interest (not exceeding that value) and returns its index within an array * * @dev An array to search in is `votingPowerHistory[_to][i].blockNumber`, * it is sorted in ascending order (blockNumber increases) * * @param _to an address of the delegate to get an array for * @param n value of interest to look for * @return an index of the closest element in an array to the value * of interest (not exceeding that value) */ function __binaryLookup(address _to, uint256 n) private view returns(uint256) { // get a link to an array of voting power history records for an address specified VotingPowerRecord[] storage history = votingPowerHistory[_to]; // left bound of the search interval, originally start of the array uint256 i = 0; // right bound of the search interval, originally end of the array uint256 j = history.length - 1; // the iteration process narrows down the bounds by // splitting the interval in a half oce per each iteration while(j > i) { // get an index in the middle of the interval [i, j] uint256 k = j - (j - i) / 2; // read an element to compare it with the value of interest VotingPowerRecord memory cp = history[k]; // if we've got a strict equal - we're lucky and done if(cp.blockNumber == n) { // just return the result - index `k` return k; } // if the value of interest is bigger - move left bound to the middle else if (cp.blockNumber < n) { // move left bound `i` to the middle position `k` i = k; } // otherwise, when the value of interest is smaller - move right bound to the middle else { // move right bound `j` to the middle position `k - 1`: // element at position `k` is bigger and cannot be the result j = k - 1; } } // reaching that point means no exact match found // since we're interested in the element which is not bigger than the // element of interest, we return the lower bound `i` return i; } } // ===== End: DAO Support (Compound-like voting delegation) ===== // SPDX-License-Identifier: MIT pragma solidity 0.8.1; /** * @title Access Control List * * @notice Access control smart contract provides an API to check * if specific operation is permitted globally and/or * if particular user has a permission to execute it. * * @notice It deals with two main entities: features and roles. * * @notice Features are designed to be used to enable/disable specific * functions (public functions) of the smart contract for everyone. * @notice User roles are designed to restrict access to specific * functions (restricted functions) of the smart contract to some users. * * @notice Terms "role", "permissions" and "set of permissions" have equal meaning * in the documentation text and may be used interchangeably. * @notice Terms "permission", "single permission" implies only one permission bit set. * * @dev This smart contract is designed to be inherited by other * smart contracts which require access control management capabilities. * * @author Basil Gorin */ contract AccessControl { /** * @notice Access manager is responsible for assigning the roles to users, * enabling/disabling global features of the smart contract * @notice Access manager can add, remove and update user roles, * remove and update global features * * @dev Role ROLE_ACCESS_MANAGER allows modifying user roles and global features * @dev Role ROLE_ACCESS_MANAGER has single bit at position 255 enabled */ uint256 public constant ROLE_ACCESS_MANAGER = 0x8000000000000000000000000000000000000000000000000000000000000000; /** * @dev Bitmask representing all the possible permissions (super admin role) * @dev Has all the bits are enabled (2^256 - 1 value) */ uint256 private constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF... /** * @notice Privileged addresses with defined roles/permissions * @notice In the context of ERC20/ERC721 tokens these can be permissions to * allow minting or burning tokens, transferring on behalf and so on * * @dev Maps user address to the permissions bitmask (role), where each bit * represents a permission * @dev Bitmask 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF * represents all possible permissions * @dev Zero address mapping represents global features of the smart contract */ mapping(address => uint256) public userRoles; /** * @dev Fired in updateRole() and updateFeatures() * * @param _by operator which called the function * @param _to address which was granted/revoked permissions * @param _requested permissions requested * @param _actual permissions effectively set */ event RoleUpdated(address indexed _by, address indexed _to, uint256 _requested, uint256 _actual); /** * @notice Creates an access control instance, * setting contract creator to have full privileges */ constructor() { // contract creator has full privileges userRoles[msg.sender] = FULL_PRIVILEGES_MASK; } /** * @notice Retrieves globally set of features enabled * * @dev Auxiliary getter function to maintain compatibility with previous * versions of the Access Control List smart contract, where * features was a separate uint256 public field * * @return 256-bit bitmask of the features enabled */ function features() public view returns(uint256) { // according to new design features are stored in zero address // mapping of `userRoles` structure return userRoles[address(0)]; } /** * @notice Updates set of the globally enabled features (`features`), * taking into account sender's permissions * * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission * @dev Function is left for backward compatibility with older versions * * @param _mask bitmask representing a set of features to enable/disable */ function updateFeatures(uint256 _mask) public { // delegate call to `updateRole` updateRole(address(0), _mask); } /** * @notice Updates set of permissions (role) for a given user, * taking into account sender's permissions. * * @dev Setting role to zero is equivalent to removing an all permissions * @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to * copying senders' permissions (role) to the user * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission * * @param operator address of a user to alter permissions for or zero * to alter global features of the smart contract * @param role bitmask representing a set of permissions to * enable/disable for a user specified */ function updateRole(address operator, uint256 role) public { // caller must have a permission to update user roles require(isSenderInRole(ROLE_ACCESS_MANAGER), "insufficient privileges (ROLE_ACCESS_MANAGER required)"); // evaluate the role and reassign it userRoles[operator] = evaluateBy(msg.sender, userRoles[operator], role); // fire an event emit RoleUpdated(msg.sender, operator, role, userRoles[operator]); } /** * @notice Determines the permission bitmask an operator can set on the * target permission set * @notice Used to calculate the permission bitmask to be set when requested * in `updateRole` and `updateFeatures` functions * * @dev Calculated based on: * 1) operator's own permission set read from userRoles[operator] * 2) target permission set - what is already set on the target * 3) desired permission set - what do we want set target to * * @dev Corner cases: * 1) Operator is super admin and its permission set is `FULL_PRIVILEGES_MASK`: * `desired` bitset is returned regardless of the `target` permission set value * (what operator sets is what they get) * 2) Operator with no permissions (zero bitset): * `target` bitset is returned regardless of the `desired` value * (operator has no authority and cannot modify anything) * * @dev Example: * Consider an operator with the permissions bitmask 00001111 * is about to modify the target permission set 01010101 * Operator wants to set that permission set to 00110011 * Based on their role, an operator has the permissions * to update only lowest 4 bits on the target, meaning that * high 4 bits of the target set in this example is left * unchanged and low 4 bits get changed as desired: 01010011 * * @param operator address of the contract operator which is about to set the permissions * @param target input set of permissions to operator is going to modify * @param desired desired set of permissions operator would like to set * @return resulting set of permissions given operator will set */ function evaluateBy(address operator, uint256 target, uint256 desired) public view returns(uint256) { // read operator's permissions uint256 p = userRoles[operator]; // taking into account operator's permissions, // 1) enable the permissions desired on the `target` target |= p & desired; // 2) disable the permissions desired on the `target` target &= FULL_PRIVILEGES_MASK ^ (p & (FULL_PRIVILEGES_MASK ^ desired)); // return calculated result return target; } /** * @notice Checks if requested set of features is enabled globally on the contract * * @param required set of features to check against * @return true if all the features requested are enabled, false otherwise */ function isFeatureEnabled(uint256 required) public view returns(bool) { // delegate call to `__hasRole`, passing `features` property return __hasRole(features(), required); } /** * @notice Checks if transaction sender `msg.sender` has all the permissions required * * @param required set of permissions (role) to check against * @return true if all the permissions requested are enabled, false otherwise */ function isSenderInRole(uint256 required) public view returns(bool) { // delegate call to `isOperatorInRole`, passing transaction sender return isOperatorInRole(msg.sender, required); } /** * @notice Checks if operator has all the permissions (role) required * * @param operator address of the user to check role for * @param required set of permissions (role) to check * @return true if all the permissions requested are enabled, false otherwise */ function isOperatorInRole(address operator, uint256 required) public view returns(bool) { // delegate call to `__hasRole`, passing operator's permissions (role) return __hasRole(userRoles[operator], required); } /** * @dev Checks if role `actual` contains all the permissions required `required` * * @param actual existent role * @param required required role * @return true if actual has required role (all permissions), false otherwise */ function __hasRole(uint256 actual, uint256 required) internal pure returns(bool) { // check the bitmask for the role required and return the result return actual & required == required; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.1; /** * @title Address Utils * * @dev Utility library of inline functions on addresses * * @author Basil Gorin */ library AddressUtils { /** * @notice Checks if the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param addr address to check * @return whether the target address is a contract */ function isContract(address addr) internal view returns (bool) { // a variable to load `extcodesize` to uint256 size = 0; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 for more details about how this works. // TODO: Check this again before the Serenity release, because all addresses will be contracts. // solium-disable-next-line security/no-inline-assembly assembly { // retrieve the size of the code at address `addr` size := extcodesize(addr) } // positive size indicates a smart contract address return size > 0; } }
File 3 of 5: ILVPool
// SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import { SafeCast } from "./libraries/SafeCast.sol"; import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import { V2Migrator } from "./base/V2Migrator.sol"; import { CorePool } from "./base/CorePool.sol"; import { ErrorHandler } from "./libraries/ErrorHandler.sol"; import { Stake } from "./libraries/Stake.sol"; import { IFactory } from "./interfaces/IFactory.sol"; import { ICorePool } from "./interfaces/ICorePool.sol"; import { ICorePoolV1 } from "./interfaces/ICorePoolV1.sol"; import { SushiLPPool } from "./SushiLPPool.sol"; /** * @title ILV Pool * * @dev ILV Pool contract to be deployed, with all base contracts inherited. * @dev Extends functionality working as a router to SushiLP Pool and deployed flash pools. * through functions like `claimYieldRewardsMultiple()` and `claimVaultRewardsMultiple()`, * ILV Pool is trusted by other pools and verified by the factory to aggregate functions * and add quality of life features for stakers. */ contract ILVPool is Initializable, V2Migrator { using ErrorHandler for bytes4; using Stake for uint256; using SafeERC20Upgradeable for IERC20Upgradeable; using SafeCast for uint256; using BitMaps for BitMaps.BitMap; /// @dev stores merkle root related to users yield weight in v1. bytes32 public merkleRoot; /// @dev bitmap mapping merkle tree user indexes to a bit that tells /// whether a user has already migrated yield or not. BitMaps.BitMap internal _usersMigrated; /// @dev maps `keccak256(userAddress,stakeId)` to a bool value that tells /// if a v1 yield has already been minted by v2 contract. mapping(address => mapping(uint256 => bool)) public v1YieldMinted; /// @dev Used to calculate vault (revenue distribution) rewards, keeps track /// of the correct ILV balance in the v1 core pool. uint256 public v1PoolTokenReserve; /** * @dev logs `_migratePendingRewards()` * * @param from user address * @param pendingRewardsMigrated value of pending rewards migrated * @param useSILV whether user claimed v1 pending rewards as ILV or sILV */ event LogMigratePendingRewards(address indexed from, uint256 pendingRewardsMigrated, bool useSILV); /** * @dev logs `_migrateYieldWeights()` * * @param from user address * @param yieldWeightMigrated total amount of weight coming from yield in v1 * */ event LogMigrateYieldWeight(address indexed from, uint256 yieldWeightMigrated); /** * @dev logs `mintV1YieldMultiple()`. * * @param from user address * @param value number of ILV tokens minted * */ event LogV1YieldMintedMultiple(address indexed from, uint256 value); /// @dev Calls `__V2Migrator_init()`. function initialize( address ilv_, address silv_, address _poolToken, address factory_, uint64 _initTime, uint32 _weight, address _corePoolV1, uint256 v1StakeMaxPeriod_ ) external initializer { // calls internal v2 migrator initializer __V2Migrator_init(ilv_, silv_, _poolToken, _corePoolV1, factory_, _initTime, _weight, v1StakeMaxPeriod_); } /** * @dev Updates value that keeps track of v1 global locked tokens weight. * * @param _v1PoolTokenReserve new value to be stored */ function setV1PoolTokenReserve(uint256 _v1PoolTokenReserve) external virtual { // only factory controller can update the _v1GlobalWeight _requireIsFactoryController(); // update v1PoolTokenReserve state variable v1PoolTokenReserve = _v1PoolTokenReserve; } /// @inheritdoc CorePool function getTotalReserves() external view virtual override returns (uint256 totalReserves) { totalReserves = poolTokenReserve + v1PoolTokenReserve; } /** * @dev Sets the yield weight tree root. * @notice Can only be called by the eDAO. * * @param _merkleRoot 32 bytes tree root. */ function setMerkleRoot(bytes32 _merkleRoot) external virtual { // checks if function is being called by PoolFactory.owner() _requireIsFactoryController(); // stores the merkle root merkleRoot = _merkleRoot; } /** * @dev Returns whether an user of a given _index in the bitmap has already * migrated v1 yield weight stored in the merkle tree or not. * * @param _index user index in the bitmap, can be checked in the off-chain * merkle tree * @return whether user has already migrated yield weights or not */ function hasMigratedYield(uint256 _index) public view returns (bool) { // checks if the merkle tree index linked to a user address has a bit of // value 0 or 1 return _usersMigrated.get(_index); } /** * @dev Executed by other core pools and flash pools * as part of yield rewards processing logic (`_claimYieldRewards()` function). * @dev Executed when _useSILV is false and pool is not an ILV pool - * see `CorePool._processRewards()`. * * @param _staker an address which stakes (the yield reward) * @param _value amount to be staked (yield reward amount) */ function stakeAsPool(address _staker, uint256 _value) external nonReentrant { // checks if contract is paused _requireNotPaused(); // expects caller to be a valid contract registered by the pool factory this.stakeAsPool.selector.verifyAccess(_factory.poolExists(msg.sender)); // gets storage pointer to user User storage user = users[_staker]; // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(_staker); // update user state _updateReward(_staker, v1WeightToAdd); // calculates take weight based on how much yield has been generated // (by checking _value) and multiplies by the 2e6 constant, since // yield is always locked for a year. uint256 stakeWeight = _value * Stake.YIELD_STAKE_WEIGHT_MULTIPLIER; // initialize new yield stake being created in memory Stake.Data memory newStake = Stake.Data({ value: (_value).toUint120(), lockedFrom: (_now256()).toUint64(), lockedUntil: (_now256() + Stake.MAX_STAKE_PERIOD).toUint64(), isYield: true }); // sum new yield stake weight to user's total weight user.totalWeight += (stakeWeight).toUint248(); // add the new yield stake to storage user.stakes.push(newStake); // update global weight and global pool token count globalWeight += stakeWeight; poolTokenReserve += _value; // emits an event emit LogStake( msg.sender, _staker, (user.stakes.length - 1), _value, (_now256() + Stake.MAX_STAKE_PERIOD).toUint64() ); } /** * @dev Calls internal `_migrateLockedStakes`, `_migrateYieldWeights` * and `_migratePendingRewards` functions for a complete migration * of a v1 user to v2. * @dev See `_migrateLockedStakes` and _`migrateYieldWeights`. */ function executeMigration( bytes32[] calldata _proof, uint256 _index, uint248 _yieldWeight, uint256 _pendingV1Rewards, bool _useSILV, uint256[] calldata _stakeIds ) external virtual { // verifies that user isn't a v1 blacklisted user _requireNotBlacklisted(msg.sender); // checks if contract is paused _requireNotPaused(); // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(msg.sender); // update user state _updateReward(msg.sender, v1WeightToAdd); // call internal migrate locked stake function // which does the loop to store each v1 stake // reference in v2 and all required data _migrateLockedStakes(_stakeIds); // checks if user is also migrating the v1 yield accumulated weight if (_yieldWeight > 0) { // if that's the case, passes the merkle proof with the user index // in the merkle tree, and the yield weight being migrated // which will be verified, and then update user state values by the // internal function _migrateYieldWeights(_proof, _index, _yieldWeight, _pendingV1Rewards, _useSILV); } } /** * @dev Calls multiple pools claimYieldRewardsFromRouter() in order to claim yield * in 1 transaction. * * @notice ILV pool works as a router for claiming multiple pools registered * in the factory. * * @param _pools array of pool addresses * @param _useSILV array of bool values telling if the pool should claim reward * as ILV or sILV */ function claimYieldRewardsMultiple(address[] calldata _pools, bool[] calldata _useSILV) external virtual { // checks if contract is paused _requireNotPaused(); // we're using selector to simplify input and access validation bytes4 fnSelector = this.claimYieldRewardsMultiple.selector; // checks if user passed the correct number of inputs fnSelector.verifyInput(_pools.length == _useSILV.length, 0); // loops over each pool passed to execute the necessary checks, and call // the functions according to the pool for (uint256 i = 0; i < _pools.length; i++) { // gets current pool in the loop address pool = _pools[i]; // verifies that the given pool is a valid pool registered by the pool // factory contract fnSelector.verifyAccess(IFactory(_factory).poolExists(pool)); // if the pool passed is the ILV pool (i.e this contract), // just calls an internal function if (ICorePool(pool).poolToken() == _ilv) { // call internal _claimYieldRewards _claimYieldRewards(msg.sender, _useSILV[i]); } else { // if not, executes a calls to the other core pool which will handle // the other pool reward claim SushiLPPool(pool).claimYieldRewardsFromRouter(msg.sender, _useSILV[i]); } } } /** * @dev Calls multiple pools claimVaultRewardsFromRouter() in order to claim yield * in 1 transaction. * * @notice ILV pool works as a router for claiming multiple pools registered * in the factory * * @param _pools array of pool addresses */ function claimVaultRewardsMultiple(address[] calldata _pools) external virtual { // checks if contract is paused _requireNotPaused(); // loops over each pool passed to execute the necessary checks, and call // the functions according to the pool for (uint256 i = 0; i < _pools.length; i++) { // gets current pool in the loop address pool = _pools[i]; // we're using selector to simplify input and state validation // checks if the given pool is a valid one registred by the pool // factory contract this.claimVaultRewardsMultiple.selector.verifyAccess(IFactory(_factory).poolExists(pool)); // if the pool passed is the ILV pool (i.e this contract), // just calls an internal function if (ICorePool(pool).poolToken() == _ilv) { // call internal _claimVaultRewards _claimVaultRewards(msg.sender); } else { // if not, executes a calls to the other core pool which will handle // the other pool reward claim SushiLPPool(pool).claimVaultRewardsFromRouter(msg.sender); } } } /** * @dev Aggregates in one single mint call multiple yield stakeIds from v1. * @dev reads v1 ILV pool to execute checks, if everything is correct, it stores * in memory total amount of yield to be minted and calls the PoolFactory to mint * it to msg.sender. * * @notice V1 won't be able to mint ILV yield anymore. This mean only this function * in the V2 contract is able to mint previously accumulated V1 yield. * * @param _stakeIds array of yield ids in v1 from msg.sender user */ function mintV1YieldMultiple(uint256[] calldata _stakeIds) external virtual { // we're using function selector to simplify validation bytes4 fnSelector = this.mintV1YieldMultiple.selector; // verifies that user isn't a v1 blacklisted user _requireNotBlacklisted(msg.sender); // checks if contract is paused _requireNotPaused(); // gets storage pointer to the user User storage user = users[msg.sender]; // initialize variables that will be used inside the loop // to store how much yield needs to be minted and how much // weight needs to be removed from the user uint256 amountToMint; uint256 weightToRemove; // initializes variable that will store how much v1 weight the user has uint256 v1WeightToAdd; // avoids stack too deep error { // uses v1 weight values for rewards calculations uint256 _v1WeightToAdd = _useV1Weight(msg.sender); // update user state _updateReward(msg.sender, _v1WeightToAdd); v1WeightToAdd = _v1WeightToAdd; } // loops over each stake id, doing the necessary checks and // updating the mapping that keep tracks of v1 yield mints. for (uint256 i = 0; i < _stakeIds.length; i++) { // gets current stake id in the loop uint256 _stakeId = _stakeIds[i]; // call v1 core pool to get all required data associated with // the passed v1 stake id (uint256 tokenAmount, uint256 _weight, uint64 lockedFrom, uint64 lockedUntil, bool isYield) = ICorePoolV1( corePoolV1 ).getDeposit(msg.sender, _stakeId); // checks if the obtained v1 stake (through getDeposit) // is indeed yield fnSelector.verifyState(isYield, i * 3); // expects the yield v1 stake to be unlocked fnSelector.verifyState(_now256() > lockedUntil, i * 4 + 1); // expects that the v1 stake hasn't been minted yet fnSelector.verifyState(!v1YieldMinted[msg.sender][_stakeId], i * 5 + 2); // verifies if the yield has been created before v2 launches fnSelector.verifyState(lockedFrom < _v1StakeMaxPeriod, i * 6 + 3); // marks v1 yield as minted v1YieldMinted[msg.sender][_stakeId] = true; // updates variables that will be used for minting yield and updating // user struct later amountToMint += tokenAmount; weightToRemove += _weight; } // subtracts value accumulated during the loop user.totalWeight -= (weightToRemove).toUint248(); // subtracts weight and token value from global variables globalWeight -= weightToRemove; // gets token value by dividing by yield weight multiplier poolTokenReserve -= (weightToRemove) / Stake.YIELD_STAKE_WEIGHT_MULTIPLIER; // expects the factory to mint ILV yield to the msg.sender user // after all checks and calculations have been successfully // executed _factory.mintYieldTo(msg.sender, amountToMint, false); // emits an event emit LogV1YieldMintedMultiple(msg.sender, amountToMint); } /** * @dev Verifies a proof from the yield weights merkle, and if it's valid, * adds the v1 user yield weight to the v2 `user.totalWeight`. * @dev The yield weights merkle tree will be published after the initial contracts * deployment, and then the merkle root is added through `setMerkleRoot` function. * * @param _proof bytes32 array with the proof generated off-chain * @param _index user index in the merkle tree * @param _yieldWeight user yield weight in v1 stored by the merkle tree * @param _pendingV1Rewards user pending rewards in v1 stored by the merkle tree * @param _useSILV whether the user wants rewards in sILV token or in a v2 ILV yield stake */ function _migrateYieldWeights( bytes32[] calldata _proof, uint256 _index, uint256 _yieldWeight, uint256 _pendingV1Rewards, bool _useSILV ) internal virtual { // gets storage pointer to the user User storage user = users[msg.sender]; // bytes4(keccak256("_migrateYieldWeights(bytes32[],uint256,uint256)"))) bytes4 fnSelector = 0x660e5908; // requires that the user hasn't migrated the yield yet fnSelector.verifyAccess(!hasMigratedYield(_index)); // compute leaf and verify merkle proof bytes32 leaf = keccak256(abi.encodePacked(_index, msg.sender, _yieldWeight, _pendingV1Rewards)); // verifies the merkle proof and requires the return value to be true fnSelector.verifyInput(MerkleProof.verify(_proof, merkleRoot, leaf), 0); // gets the value compounded into v2 as ILV yield to be added into v2 user.totalWeight uint256 pendingRewardsCompounded = _migratePendingRewards(_pendingV1Rewards, _useSILV); uint256 weightCompounded = pendingRewardsCompounded * Stake.YIELD_STAKE_WEIGHT_MULTIPLIER; uint256 ilvYieldMigrated = _yieldWeight / Stake.YIELD_STAKE_WEIGHT_MULTIPLIER; // add v1 yield weight to the v2 user user.totalWeight += (_yieldWeight + weightCompounded).toUint248(); // adds v1 pending yield compounded + v1 total yield to global weight // and poolTokenReserve in the v2 contract. globalWeight += (weightCompounded + _yieldWeight); poolTokenReserve += (pendingRewardsCompounded + ilvYieldMigrated); // set user as claimed in bitmap _usersMigrated.set(_index); // emits an event emit LogMigrateYieldWeight(msg.sender, _yieldWeight); } /** * @dev Gets pending rewards in the v1 ilv pool and v1 lp pool stored in the merkle tree, * and allows the v1 users of those pools to claim them as ILV compounded in the v2 pool or * sILV minted to their wallet. * @dev Eligible users are filtered and stored in the merkle tree. * * @param _pendingV1Rewards user pending rewards in v1 stored by the merkle tree * @param _useSILV whether the user wants rewards in sILV token or in a v2 ILV yield stake * * @return pendingRewardsCompounded returns the value compounded into the v2 pool (if the user selects ILV) */ function _migratePendingRewards(uint256 _pendingV1Rewards, bool _useSILV) internal virtual returns (uint256 pendingRewardsCompounded) { // gets pointer to user User storage user = users[msg.sender]; // if the user (msg.sender) wants to mint pending rewards as sILV, simply mint if (_useSILV) { // calls the factory to mint sILV _factory.mintYieldTo(msg.sender, _pendingV1Rewards, _useSILV); } else { // otherwise we create a new v2 yield stake (ILV) Stake.Data memory stake = Stake.Data({ value: (_pendingV1Rewards).toUint120(), lockedFrom: (_now256()).toUint64(), lockedUntil: (_now256() + Stake.MAX_STAKE_PERIOD).toUint64(), isYield: true }); // adds new ILV yield stake to user array // notice that further values will be updated later in execution // (user.totalWeight, user.subYieldRewards, user.subVaultRewards, ...) user.stakes.push(stake); // updates function's return value pendingRewardsCompounded = _pendingV1Rewards; } // emits an event emit LogMigratePendingRewards(msg.sender, _pendingV1Rewards, _useSILV); } /** * @inheritdoc CorePool * @dev In the ILV Pool we verify that the user isn't coming from v1. * @dev If user has weight in v1, we can't allow them to call this * function, otherwise it would throw an error in the new address when calling * mintV1YieldMultiple if the user migrates. */ function moveFundsFromWallet(address _to) public virtual override { // we're using function selector to simplify validation bytes4 fnSelector = this.moveFundsFromWallet.selector; // we query v1 ilv pool contract (, uint256 totalWeight, , ) = ICorePoolV1(corePoolV1).users(msg.sender); // we check that the v1 total weight is 0 i.e the user can't have any yield fnSelector.verifyState(totalWeight == 0, 0); // call parent moveFundsFromWalet which contains further checks and the actual // execution super.moveFundsFromWallet(_to); } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[46] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. */ abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. */ bool private _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool private _initializing; /** * @dev Modifier to protect an initializer function from being invoked twice. */ modifier initializer() { require(_initializing || !_initialized, "Initializable: contract is already initialized"); bool isTopLevelCall = !_initializing; if (isTopLevelCall) { _initializing = true; _initialized = true; } _; if (isTopLevelCall) { _initializing = false; } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20Upgradeable { /** * @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: MIT pragma solidity ^0.8.0; import "../IERC20Upgradeable.sol"; import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable { using AddressUpgradeable for address; function safeTransfer( IERC20Upgradeable token, address to, uint256 value ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom( IERC20Upgradeable 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( IERC20Upgradeable 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)); } function safeIncreaseAllowance( IERC20Upgradeable token, address spender, uint256 value ) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance( IERC20Upgradeable token, address spender, uint256 value ) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); uint256 newAllowance = oldAllowance - value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } } /** * @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(IERC20Upgradeable 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"); if (returndata.length > 0) { // Return data is optional require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol) pragma solidity 0.8.4; import { ErrorHandler } from "./ErrorHandler.sol"; /** * @notice Copied from OpenZeppelin's SafeCast.sol, adapted to use just in the required * uint sizes. * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such 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. * * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing * all math on `uint256` and `int256` and then downcasting. */ library SafeCast { using ErrorHandler for bytes4; /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits */ function toUint248(uint256 _value) internal pure returns (uint248) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint248(uint256))"))` bytes4 fnSelector = 0x3fd72672; fnSelector.verifyInput(_value <= type(uint248).max, 0); return uint248(_value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 _value) internal pure returns (uint128) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint128(uint256))"))` bytes4 fnSelector = 0x809fdd33; fnSelector.verifyInput(_value <= type(uint128).max, 0); return uint128(_value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits */ function toUint120(uint256 _value) internal pure returns (uint120) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint120(uint256))"))` bytes4 fnSelector = 0x1e4e4bad; fnSelector.verifyInput(_value <= type(uint120).max, 0); return uint120(_value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 _value) internal pure returns (uint64) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint64(uint256))"))` bytes4 fnSelector = 0x2665fad0; fnSelector.verifyInput(_value <= type(uint64).max, 0); return uint64(_value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 _value) internal pure returns (uint32) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint32(uint256))"))` bytes4 fnSelector = 0xc8193255; fnSelector.verifyInput(_value <= type(uint32).max, 0); return uint32(_value); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. * Largelly inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor]. */ library BitMaps { struct BitMap { mapping(uint256 => uint256) _data; } /** * @dev Returns whether the bit at `index` is set. */ function get(BitMap storage bitmap, uint256 index) internal view returns (bool) { uint256 bucket = index >> 8; uint256 mask = 1 << (index & 0xff); return bitmap._data[bucket] & mask != 0; } /** * @dev Sets the bit at `index` to the boolean `value`. */ function setTo( BitMap storage bitmap, uint256 index, bool value ) internal { if (value) { set(bitmap, index); } else { unset(bitmap, index); } } /** * @dev Sets the bit at `index`. */ function set(BitMap storage bitmap, uint256 index) internal { uint256 bucket = index >> 8; uint256 mask = 1 << (index & 0xff); bitmap._data[bucket] |= mask; } /** * @dev Unsets the bit at `index`. */ function unset(BitMap storage bitmap, uint256 index) internal { uint256 bucket = index >> 8; uint256 mask = 1 << (index & 0xff); bitmap._data[bucket] &= ~mask; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev These functions deal with verification of Merkle Trees proofs. * * The proofs can be generated using the JavaScript library * https://github.com/miguelmota/merkletreejs[merkletreejs]. * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled. * * See `test/utils/cryptography/MerkleProof.test.js` for some examples. */ library MerkleProof { /** * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree * defined by `root`. For this, a `proof` must be provided, containing * sibling hashes on the branch from the leaf to the root of the tree. Each * pair of leaves and each pair of pre-images are assumed to be sorted. */ function verify( bytes32[] memory proof, bytes32 root, bytes32 leaf ) internal pure returns (bool) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { // Hash(current element of the proof + current computed hash) computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); } } // Check if the computed hash (root) is equal to the provided root return computedHash == root; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { ICorePoolV1 } from "../interfaces/ICorePoolV1.sol"; import { ErrorHandler } from "../libraries/ErrorHandler.sol"; import { Stake } from "../libraries/Stake.sol"; import { CorePool } from "./CorePool.sol"; /** * @title V2Migrator * * @dev V2Migrator inherits all CorePool base contract functionaltiy, and adds * v1 to v2 migration related functions. This is a core smart contract of * Sushi LP and ILV pools, and manages users locked and yield weights coming * from v1. * @dev Parameters need to be reviewed carefully before deployment for the migration process. * @dev Users will migrate their locked stakes, which are stored in the contract, * and v1 total yield weights by data stored in a merkle tree using merkle proofs. */ abstract contract V2Migrator is Initializable, CorePool { using ErrorHandler for bytes4; using Stake for uint256; /// @dev Maps v1 addresses that are black listed for v2 migration. mapping(address => bool) public isBlacklisted; /// @dev Stores maximum timestamp of a v1 stake (yield) accepted in v2. uint256 internal _v1StakeMaxPeriod; /// @dev Stores maximum timestamp of a v1 stake (deposit) accepted in v2. uint256 internal constant _v1DepositMaxPeriod = 1648150500; /** * @dev logs `_migrateLockedStakes()` * * @param from user address * @param totalV1WeightAdded total amount of weight coming from locked stakes in v1 * */ event LogMigrateLockedStakes(address indexed from, uint256 totalV1WeightAdded); /** * @dev V2Migrator initializer function. * * @param v1StakeMaxPeriod_ max timestamp that we accept _lockedFrom values * in v1 stakes */ function __V2Migrator_init( address ilv_, address silv_, address _poolToken, address _corePoolV1, address factory_, uint64 _initTime, uint32 _weight, uint256 v1StakeMaxPeriod_ ) internal initializer { // call internal core pool intializar __CorePool_init(ilv_, silv_, _poolToken, _corePoolV1, factory_, _initTime, _weight); // sets max period for upgrading to V2 contracts i.e migrating _v1StakeMaxPeriod = v1StakeMaxPeriod_; } /** * @notice Blacklists a list of v1 user addresses by setting the * _isBlacklisted flag to true. * * @dev The intention is to prevent addresses that exploited v1 to be able to move * stake ids to the v2 contracts and to be able to mint any yield from a v1 * stake id with the isYield flag set to true. * * @param _users v1 users address array */ function blacklistUsers(address[] calldata _users) external virtual { // only the factory controller can blacklist users _requireIsFactoryController(); // we're using selector to simplify validation bytes4 fnSelector = this.blacklistUsers.selector; // gets each user in the array to be blacklisted for (uint256 i = 0; i < _users.length; i++) { // makes sure user passed isn't the address 0 fnSelector.verifyInput(_users[i] != address(0), 0); // updates mapping isBlacklisted[_users[i]] = true; } } /** * @dev External migrateLockedStakes call, used in the Sushi LP pool contract. * @dev The function is used by users that want to migrate locked stakes in v1, * but have no yield in the pool. This happens in two scenarios: * * 1 - The user pool is the Sushi LP pool, which only has stakes; * 2 - The user joined ILV pool recently, doesn't have much yield and * doesn't want to migrate their yield weight in the pool; * @notice Most of the times this function will be used in the inherited Sushi * LP pool contract (called by the v1 user coming from sushi pool), * but it's possible that a v1 user coming from the ILV pool decides * to use this function instead of `executeMigration()` defined in * the ILV pool contract. * * @param _stakeIds array of v1 stake ids */ function migrateLockedStakes(uint256[] calldata _stakeIds) external virtual { // verifies that user isn't a v1 blacklisted user _requireNotBlacklisted(msg.sender); // checks if contract is paused _requireNotPaused(); // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(msg.sender); // update user state _updateReward(msg.sender, v1WeightToAdd); // call internal migrate locked stake function // which does the loop to store each v1 stake // reference in v2 and all required data _migrateLockedStakes(_stakeIds); } /** * @dev Reads v1 core pool locked stakes data (by looping through the `_stakeIds` array), * checks if it's a valid v1 stake to migrate and save the id to v2 user struct. * * @dev Only `msg.sender` can migrate v1 stakes to v2. * * @param _stakeIds array of v1 stake ids */ function _migrateLockedStakes(uint256[] calldata _stakeIds) internal virtual { User storage user = users[msg.sender]; // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("_migrateLockedStakes(uint256[])"))` bytes4 fnSelector = 0x80812525; // initializes variable which will tell how much // weight in v1 the user is bringing to v2 uint256 totalV1WeightAdded; // loops over each v1 stake id passed to do the necessary validity checks // and store the values required in v2 to keep track of v1 weight in order // to include it in v2 rewards (yield and revenue distribution) calculations for (uint256 i = 0; i < _stakeIds.length; i++) { // reads the v1 stake by calling the v1 core pool getDeposit and separates // all required data in the struct to be used (, uint256 _weight, uint64 lockedFrom, , bool isYield) = ICorePoolV1(corePoolV1).getDeposit( msg.sender, _stakeIds[i] ); // checks if the v1 stake is in the valid period for migration fnSelector.verifyState(lockedFrom <= _v1DepositMaxPeriod, i * 3); // checks if the v1 stake has been locked originally and isn't a yield // stake, which are the requirements for moving to v2 through this function fnSelector.verifyState(lockedFrom > 0 && !isYield, i * 3 + 1); // checks if the user has already brought those v1 stakes to v2 fnSelector.verifyState(v1StakesWeights[msg.sender][_stakeIds[i]] == 0, i * 3 + 2); // adds v1 weight to the dynamic mapping which will be used in calculations v1StakesWeights[msg.sender][_stakeIds[i]] = _weight; // updates the variable keeping track of the total weight migrated totalV1WeightAdded += _weight; // update value keeping track of v1 stakes ids mapping length user.v1IdsLength++; // adds stake id to mapping keeping track of each v1 stake id user.v1StakesIds[user.v1IdsLength - 1] = _stakeIds[i]; } // emits an event emit LogMigrateLockedStakes(msg.sender, totalV1WeightAdded); } /** * @dev Utility used by functions that can't allow blacklisted users to call. * @dev Blocks user addresses stored in the _isBlacklisted mapping to call actions like * minting v1 yield stake ids and migrating locked stakes. */ function _requireNotBlacklisted(address _user) internal view virtual { // we're using selector to simplify input and access validation bytes4 fnSelector = this.migrateLockedStakes.selector; // makes sure that msg.sender isn't a blacklisted address fnSelector.verifyAccess(!isBlacklisted[_user]); } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[48] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import { SafeCast } from "../libraries/SafeCast.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import { Timestamp } from "./Timestamp.sol"; import { VaultRecipient } from "./VaultRecipient.sol"; import { ErrorHandler } from "../libraries/ErrorHandler.sol"; import { Stake } from "../libraries/Stake.sol"; import { IILVPool } from "../interfaces/IILVPool.sol"; import { IFactory } from "../interfaces/IFactory.sol"; import { ICorePool } from "../interfaces/ICorePool.sol"; import { ICorePoolV1 } from "../interfaces/ICorePoolV1.sol"; /** * @title Core Pool * * @notice An abstract contract containing common logic for ILV and ILV/ETH SLP pools. * * @dev Base smart contract for ILV and LP pool. Stores each pool user by mapping * its address to the user struct. User struct stores v2 stakes, which fit * in 1 storage slot each (by using the Stake lib), total weights, pending * yield and revenue distributions, and v1 stake ids. ILV and LP stakes can * be made through flexible stake mode, which only increments the flexible * balance of a given user, or through locked staking. Locked staking creates * a new Stake element fitting 1 storage slot with its value and lock duration. * When calculating pending rewards, CorePool checks v1 locked stakes weights * to increment in the calculations and stores pending yield and pending revenue * distributions. Every time a stake or unstake related function is called, * it updates pending values, but don't require instant claimings. Rewards * claiming are executed in separate functions, and in the case of yield, * it also requires the user checking whether ILV or sILV is wanted as the yield reward. * * @dev Deployment and initialization. * After proxy is deployed and attached to the implementation, it should be * registered by the PoolFactory contract * Additionally, 3 token instance addresses must be defined on deployment: * - ILV token address * - sILV token address, used to mint sILV rewards * - pool token address, it can be ILV token address, ILV/ETH pair address, and others * * @dev Pool weight defines the fraction of the yield current pool receives among the other pools, * pool factory is responsible for the weight synchronization between the pools. * @dev The weight is logically 20% for ILV pool and 80% for ILV/ETH pool initially. * It can be changed through ICCPs and new flash pools added in the protocol. * Since Solidity doesn't support fractions the weight is defined by the division of * pool weight by total pools weight (sum of all registered pools within the factory). * @dev For ILV Pool we use 200 as weight and for ILV/ETH SLP pool - 800. * */ abstract contract CorePool is Initializable, UUPSUpgradeable, VaultRecipient, ReentrancyGuardUpgradeable, PausableUpgradeable, Timestamp { using SafeERC20Upgradeable for IERC20Upgradeable; using SafeCast for uint256; using Stake for Stake.Data; using ErrorHandler for bytes4; using Stake for uint256; /// @dev Data structure representing token holder using a pool. struct User { /// @dev pending yield rewards to be claimed uint128 pendingYield; /// @dev pending revenue distribution to be claimed uint128 pendingRevDis; /// @dev Total weight uint248 totalWeight; /// @dev number of v1StakesIds uint8 v1IdsLength; /// @dev Checkpoint variable for yield calculation uint256 yieldRewardsPerWeightPaid; /// @dev Checkpoint variable for vault rewards calculation uint256 vaultRewardsPerWeightPaid; /// @dev An array of holder's stakes Stake.Data[] stakes; /// @dev A mapping of holder's stakes ids in V1 mapping(uint256 => uint256) v1StakesIds; } /// @dev Data structure used in `unstakeLockedMultiple()` function. struct UnstakeParameter { uint256 stakeId; uint256 value; } /// @dev Token holder storage, maps token holder address to their data record. mapping(address => User) public users; /// @dev Maps `keccak256(userAddress,stakeId)` to a uint256 value that tells /// a v1 locked stake weight that has already been migrated to v2 /// and is updated through _useV1Weight. mapping(address => mapping(uint256 => uint256)) public v1StakesWeights; /// @dev Link to sILV ERC20 Token instance. address internal _silv; /// @dev Link to ILV ERC20 Token instance. address internal _ilv; /// @dev Address of v1 core pool with same poolToken. address internal corePoolV1; /// @dev Link to the pool token instance, for example ILV or ILV/ETH pair. address public poolToken; /// @dev Pool weight, initial values are 200 for ILV pool and 800 for ILV/ETH. uint32 public weight; /// @dev Timestamp of the last yield distribution event. uint64 public lastYieldDistribution; /// @dev Used to calculate yield rewards. /// @dev This value is different from "reward per token" used in flash pool. /// @dev Note: stakes are different in duration and "weight" reflects that. uint256 public yieldRewardsPerWeight; /// @dev Used to calculate rewards, keeps track of the tokens weight locked in staking. uint256 public globalWeight; /// @dev Used to calculate rewards, keeps track of the correct token weight in the v1 /// core pool. uint256 public v1GlobalWeight; /// @dev Pool tokens value available in the pool; /// pool token examples are ILV (ILV core pool) or ILV/ETH pair (LP core pool). /// @dev For LP core pool this value doesnt' count for ILV tokens received as Vault rewards /// while for ILV core pool it does count for such tokens as well. uint256 public poolTokenReserve; /// @dev Flag indicating pool type, false means "core pool". bool public constant isFlashPool = false; /** * @dev Fired in _stake() and stakeAsPool() in ILVPool contract. * @param by address that executed the stake function (user or pool) * @param from token holder address, the tokens will be returned to that address * @param stakeId id of the new stake created * @param value value of tokens staked * @param lockUntil timestamp indicating when tokens should unlock (max 2 years) */ event LogStake(address indexed by, address indexed from, uint256 stakeId, uint256 value, uint64 lockUntil); /** * @dev Fired in `unstakeLocked()`. * * @param to address receiving the tokens (user) * @param stakeId id value of the stake * @param value number of tokens unstaked * @param isYield whether stake struct unstaked was coming from yield or not */ event LogUnstakeLocked(address indexed to, uint256 stakeId, uint256 value, bool isYield); /** * @dev Fired in `unstakeLockedMultiple()`. * * @param to address receiving the tokens (user) * @param totalValue total number of tokens unstaked * @param unstakingYield whether unstaked tokens had isYield flag true or false */ event LogUnstakeLockedMultiple(address indexed to, uint256 totalValue, bool unstakingYield); /** * @dev Fired in `_sync()`, `sync()` and dependent functions (stake, unstake, etc.). * * @param by an address which performed an operation * @param yieldRewardsPerWeight updated yield rewards per weight value * @param lastYieldDistribution usually, current timestamp */ event LogSync(address indexed by, uint256 yieldRewardsPerWeight, uint64 lastYieldDistribution); /** * @dev Fired in `_claimYieldRewards()`. * * @param by an address which claimed the rewards (staker or ilv pool contract * in case of a multiple claim call) * @param from an address which received the yield * @param sILV flag indicating if reward was paid (minted) in sILV * @param value value of yield paid */ event LogClaimYieldRewards(address indexed by, address indexed from, bool sILV, uint256 value); /** * @dev Fired in `_claimVaultRewards()`. * * @param by an address which claimed the rewards (staker or ilv pool contract * in case of a multiple claim call) * @param from an address which received the yield * @param value value of yield paid */ event LogClaimVaultRewards(address indexed by, address indexed from, uint256 value); /** * @dev Fired in `_updateRewards()`. * * @param by an address which processed the rewards (staker or ilv pool contract * in case of a multiple claim call) * @param from an address which received the yield * @param yieldValue value of yield processed * @param revDisValue value of revenue distribution processed */ event LogUpdateRewards(address indexed by, address indexed from, uint256 yieldValue, uint256 revDisValue); /** * @dev fired in `moveFundsFromWallet()`. * * @param from user asking migration * @param to new user address * @param previousTotalWeight total weight of `from` before moving to a new address * @param newTotalWeight total weight of `to` after moving to a new address * @param previousYield pending yield of `from` before moving to a new address * @param newYield pending yield of `to` after moving to a new address * @param previousRevDis pending revenue distribution of `from` before moving to a new address * @param newRevDis pending revenue distribution of `to` after moving to a new address */ event LogMoveFundsFromWallet( address indexed from, address indexed to, uint248 previousTotalWeight, uint248 newTotalWeight, uint128 previousYield, uint128 newYield, uint128 previousRevDis, uint128 newRevDis ); /** * @dev Fired in `receiveVaultRewards()`. * * @param by an address that sent the rewards, always a vault * @param value amount of tokens received */ event LogReceiveVaultRewards(address indexed by, uint256 value); /** * @dev Used in child contracts to initialize the pool. * * @param ilv_ ILV ERC20 Token address * @param silv_ sILV ERC20 Token address * @param _poolToken token the pool operates on, for example ILV or ILV/ETH pair * @param _corePoolV1 v1 core pool address * @param factory_ PoolFactory contract address * @param _initTime initial timestamp used to calculate the rewards * note: _initTime is set to the future effectively meaning _sync() calls will do nothing * before _initTime * @param _weight number representing the pool's weight, which in _sync calls * is used by checking the total pools weight in the PoolFactory contract */ function __CorePool_init( address ilv_, address silv_, address _poolToken, address _corePoolV1, address factory_, uint64 _initTime, uint32 _weight ) internal initializer { // we're using selector to simplify input and state validation // internal function simulated selector is // `bytes4(keccak256("__CorePool_init(address,address,address,address,address,uint64,uint32)"))` bytes4 fnSelector = 0x1512be06; // verify the inputs fnSelector.verifyNonZeroInput(uint160(_poolToken), 2); fnSelector.verifyNonZeroInput(uint160(_corePoolV1), 3); fnSelector.verifyNonZeroInput(_initTime, 5); fnSelector.verifyNonZeroInput(_weight, 6); __FactoryControlled_init(factory_); __ReentrancyGuard_init(); __Pausable_init(); // save the inputs into internal state variables _ilv = ilv_; _silv = silv_; poolToken = _poolToken; corePoolV1 = _corePoolV1; weight = _weight; // init the dependent internal state variables lastYieldDistribution = _initTime; } /** * @notice Calculates current yield rewards value available for address specified. * * @dev See `_pendingRewards()` for further details. * * @dev External `pendingRewards()` returns pendingYield and pendingRevDis * accumulated with already stored user.pendingYield and user.pendingRevDis. * * @param _staker an address to calculate yield rewards value for */ function pendingRewards(address _staker) external view virtual returns (uint256 pendingYield, uint256 pendingRevDis) { this.pendingRewards.selector.verifyNonZeroInput(uint160(_staker), 0); // `newYieldRewardsPerWeight` will be the stored or recalculated value for `yieldRewardsPerWeight` uint256 newYieldRewardsPerWeight; // gas savings uint256 _lastYieldDistribution = lastYieldDistribution; // based on the rewards per weight value, calculate pending rewards; User storage user = users[_staker]; // initializes both variables from one storage slot (uint256 v1StakesLength, uint256 userWeight) = (uint256(user.v1IdsLength), uint256(user.totalWeight)); // total user v1 weight to be used uint256 totalV1Weight; if (v1StakesLength > 0) { // loops through v1StakesIds and adds v1 weight for (uint256 i = 0; i < v1StakesLength; i++) { uint256 stakeId = user.v1StakesIds[i]; (, uint256 _weight, , , ) = ICorePoolV1(corePoolV1).getDeposit(_staker, stakeId); uint256 storedWeight = v1StakesWeights[_staker][stakeId]; totalV1Weight += _weight <= storedWeight ? _weight : storedWeight; } } // if smart contract state was not updated recently, `yieldRewardsPerWeight` value // is outdated and we need to recalculate it in order to calculate pending rewards correctly if (_now256() > _lastYieldDistribution && globalWeight != 0) { uint256 endTime = _factory.endTime(); uint256 multiplier = _now256() > endTime ? endTime - _lastYieldDistribution : _now256() - _lastYieldDistribution; uint256 ilvRewards = (multiplier * weight * _factory.ilvPerSecond()) / _factory.totalWeight(); // recalculated value for `yieldRewardsPerWeight` newYieldRewardsPerWeight = ilvRewards.getRewardPerWeight((globalWeight + v1GlobalWeight)) + yieldRewardsPerWeight; } else { // if smart contract state is up to date, we don't recalculate newYieldRewardsPerWeight = yieldRewardsPerWeight; } pendingYield = (userWeight + totalV1Weight).earned(newYieldRewardsPerWeight, user.yieldRewardsPerWeightPaid) + user.pendingYield; pendingRevDis = (userWeight + totalV1Weight).earned(vaultRewardsPerWeight, user.vaultRewardsPerWeightPaid) + user.pendingRevDis; } /** * @notice Returns total staked token balance for the given address. * @dev Loops through stakes and returns total balance. * @notice Expected to be called externally through `eth_call`. Gas shouldn't * be an issue here. * * @param _user an address to query balance for * @return balance total staked token balance */ function balanceOf(address _user) external view virtual returns (uint256 balance) { // gets storage pointer to _user User storage user = users[_user]; // loops over each user stake and adds to the total balance. for (uint256 i = 0; i < user.stakes.length; i++) { balance += user.stakes[i].value; } } /** * @dev Returns the sum of poolTokenReserve with the deposit reserves in v1. * @dev In ILV Pool contract the eDAO stores the v1 reserve value, and * in the SLP pool we're able to query it from the v1 lp pool contract. */ function getTotalReserves() external view virtual returns (uint256 totalReserves); /** * @notice Returns information on the given stake for the given address. * * @dev See getStakesLength. * * @param _user an address to query stake for * @param _stakeId zero-indexed stake ID for the address specified * @return stake info as Stake structure */ function getStake(address _user, uint256 _stakeId) external view virtual returns (Stake.Data memory) { // read stake at specified index and return return users[_user].stakes[_stakeId]; } /** * @notice Returns a v1 stake id in the `user.v1StakesIds` array. * * @dev Get v1 stake id position through `getV1StakePosition()`. * * @param _user an address to query stake for * @param _position position index in the array * @return stakeId value */ function getV1StakeId(address _user, uint256 _position) external view virtual returns (uint256) { // returns the v1 stake id indicated at _position value return users[_user].v1StakesIds[_position]; } /** * @notice Returns a v1 stake position in the `user.v1StakesIds` array. * * @dev Helper function to call `getV1StakeId()`. * @dev Reverts if stakeId isn't found. * * @param _user an address to query stake for * @param _desiredId desired stakeId position in the array to find * @return position stake info as Stake structure */ function getV1StakePosition(address _user, uint256 _desiredId) external view virtual returns (uint256 position) { // gets storage pointer to user User storage user = users[_user]; // loops over each v1 stake id and checks if it's the one // that the caller is looking for for (uint256 i = 0; i < user.v1IdsLength; i++) { if (user.v1StakesIds[i] == _desiredId) { // if it's the desired stake id, return the array index (i.e position) return i; } } revert(); } /** * @notice Returns number of stakes for the given address. Allows iteration over stakes. * * @dev See `getStake()`. * * @param _user an address to query stake length for * @return number of stakes for the given address */ function getStakesLength(address _user) external view virtual returns (uint256) { // read stakes array length and return return users[_user].stakes.length; } /** * @dev Set paused/unpaused state in the pool contract. * * @param _shouldPause whether the contract should be paused/unpausd */ function pause(bool _shouldPause) external { // checks if caller is authorized to pause _requireIsFactoryController(); // checks bool input and pause/unpause the contract depending on // msg.sender's request if (_shouldPause) { _pause(); } else { _unpause(); } } /** * @notice Stakes specified value of tokens for the specified value of time, * and pays pending yield rewards if any. * * @dev Requires value to stake and lock duration to be greater than zero. * * @param _value value of tokens to stake * @param _lockDuration stake duration as unix timestamp */ function stake(uint256 _value, uint64 _lockDuration) external virtual nonReentrant { // checks if the contract is in a paused state _requireNotPaused(); // we're using selector to simplify input and state validation bytes4 fnSelector = this.stake.selector; // validate the inputs fnSelector.verifyNonZeroInput(_value, 1); fnSelector.verifyInput(_lockDuration >= Stake.MIN_STAKE_PERIOD && _lockDuration <= Stake.MAX_STAKE_PERIOD, 2); // get a link to user data struct, we will write to it later User storage user = users[msg.sender]; // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(msg.sender); // update user state _updateReward(msg.sender, v1WeightToAdd); // calculates until when a stake is going to be locked uint64 lockUntil = (_now256()).toUint64() + _lockDuration; // stake weight formula rewards for locking uint256 stakeWeight = (((lockUntil - _now256()) * Stake.WEIGHT_MULTIPLIER) / Stake.MAX_STAKE_PERIOD + Stake.BASE_WEIGHT) * _value; // makes sure stakeWeight is valid assert(stakeWeight > 0); // create and save the stake (append it to stakes array) Stake.Data memory userStake = Stake.Data({ value: (_value).toUint120(), lockedFrom: (_now256()).toUint64(), lockedUntil: lockUntil, isYield: false }); // pushes new stake to `stakes` array user.stakes.push(userStake); // update user weight user.totalWeight += (stakeWeight).toUint248(); // update global weight value and global pool token count globalWeight += stakeWeight; poolTokenReserve += _value; // transfer `_value` IERC20Upgradeable(poolToken).safeTransferFrom(address(msg.sender), address(this), _value); // emit an event emit LogStake(msg.sender, msg.sender, (user.stakes.length - 1), _value, lockUntil); } /** * @dev Moves msg.sender stake data to a new address. * @dev V1 stakes are never migrated to the new address. We process all rewards, * clean the previous user (msg.sender), add the previous user data to * the desired address and update subYieldRewards/subVaultRewards values * in order to make sure both addresses will have rewards cleaned. * * @param _to new user address, needs to be a fresh address with no stakes */ function moveFundsFromWallet(address _to) public virtual { // checks if the contract is in a paused state _requireNotPaused(); // gets storage pointer to msg.sender user struct User storage previousUser = users[msg.sender]; // gets storage pointer to desired address user struct User storage newUser = users[_to]; // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(msg.sender); // We process update global and user's rewards // before moving the user funds to a new wallet. // This way we can ensure that all v1 ids weight have been used before the v2 // stakes to a new address. _updateReward(msg.sender, v1WeightToAdd); // we're using selector to simplify input and state validation bytes4 fnSelector = this.moveFundsFromWallet.selector; // validate input is set fnSelector.verifyNonZeroInput(uint160(_to), 0); // verify new user records are empty fnSelector.verifyState( newUser.totalWeight == 0 && newUser.v1IdsLength == 0 && newUser.stakes.length == 0 && newUser.yieldRewardsPerWeightPaid == 0 && newUser.vaultRewardsPerWeightPaid == 0, 0 ); // saves previous user total weight uint248 previousTotalWeight = previousUser.totalWeight; // saves previous user pending yield uint128 previousYield = previousUser.pendingYield; // saves previous user pending rev dis uint128 previousRevDis = previousUser.pendingRevDis; // It's expected to have all previous user values // migrated to the new user address (_to). // We recalculate yield and vault rewards values // to make sure new user pending yield and pending rev dis to be stored // at newUser.pendingYield and newUser.pendingRevDis is 0, since we just processed // all pending rewards calling _updateReward. newUser.totalWeight = previousTotalWeight; newUser.pendingYield = previousYield; newUser.pendingRevDis = previousRevDis; newUser.yieldRewardsPerWeightPaid = yieldRewardsPerWeight; newUser.vaultRewardsPerWeightPaid = vaultRewardsPerWeight; newUser.stakes = previousUser.stakes; delete previousUser.totalWeight; delete previousUser.pendingYield; delete previousUser.pendingRevDis; delete previousUser.stakes; // emits an event emit LogMoveFundsFromWallet( msg.sender, _to, previousTotalWeight, newUser.totalWeight, previousYield, newUser.pendingYield, previousRevDis, newUser.pendingRevDis ); } /** * @notice Service function to synchronize pool state with current time. * * @dev Can be executed by anyone at any time, but has an effect only when * at least one second passes between synchronizations. * @dev Executed internally when staking, unstaking, processing rewards in order * for calculations to be correct and to reflect state progress of the contract. * @dev When timing conditions are not met (executed too frequently, or after factory * end time), function doesn't throw and exits silently. */ function sync() external virtual { _requireNotPaused(); // calls internal function _sync(); } /** * @dev Calls internal `_claimYieldRewards()` passing `msg.sender` as `_staker`. * * @notice Pool state is updated before calling the internal function. */ function claimYieldRewards(bool _useSILV) external virtual { // checks if the contract is in a paused state _requireNotPaused(); // calls internal function _claimYieldRewards(msg.sender, _useSILV); } /** * @dev Calls internal `_claimVaultRewards()` passing `msg.sender` as `_staker`. * * @notice Pool state is updated before calling the internal function. */ function claimVaultRewards() external virtual { // checks if the contract is in a paused state _requireNotPaused(); // calls internal function _claimVaultRewards(msg.sender); } /** * @dev Claims both revenue distribution and yield rewards in one call. * */ function claimAllRewards(bool _useSILV) external virtual { // checks if the contract is in a paused state _requireNotPaused(); // calls internal yield and vault rewards functions _claimVaultRewards(msg.sender); _claimYieldRewards(msg.sender, _useSILV); } /** * @dev Executed by the vault to transfer vault rewards ILV from the vault * into the pool. * * @dev This function is executed only for ILV core pools. * * @param _value amount of ILV rewards to transfer into the pool */ function receiveVaultRewards(uint256 _value) external virtual { // always sync the pool state vars before moving forward _sync(); // checks if the contract is in a paused state _requireNotPaused(); // checks if msg.sender is the vault contract _requireIsVault(); // we're using selector to simplify input and state validation bytes4 fnSelector = this.receiveVaultRewards.selector; // return silently if there is no reward to receive if (_value == 0) { return; } // verify weight is not zero fnSelector.verifyState(globalWeight > 0 || v1GlobalWeight > 0, 0); // we update vaultRewardsPerWeight value using v1 and v2 global weight, // expecting to distribute revenue distribution correctly to all users // coming from v1 and new v2 users. vaultRewardsPerWeight += _value.getRewardPerWeight(globalWeight + v1GlobalWeight); // transfers ILV from the Vault contract to the pool IERC20Upgradeable(_ilv).safeTransferFrom(msg.sender, address(this), _value); // emits an event emit LogReceiveVaultRewards(msg.sender, _value); } /** * @dev Updates value that keeps track of v1 global locked tokens weight. * * @param _v1GlobalWeight new value to be stored */ function setV1GlobalWeight(uint256 _v1GlobalWeight) external virtual { // only factory controller can update the _v1GlobalWeight _requireIsFactoryController(); // update v1GlobalWeight state variable v1GlobalWeight = _v1GlobalWeight; } /** * @dev Executed by the factory to modify pool weight; the factory is expected * to keep track of the total pools weight when updating. * * @dev Set weight to zero to disable the pool. * * @param _weight new weight to set for the pool */ function setWeight(uint32 _weight) external virtual { // update pool state using current weight value _sync(); // verify function is executed by the factory this.setWeight.selector.verifyAccess(msg.sender == address(_factory)); // set the new weight value weight = _weight; } /** * @dev Unstakes a stake that has been previously locked, and is now in an unlocked * state. If the stake has the isYield flag set to true, then the contract * requests ILV to be minted by the PoolFactory. Otherwise it transfers ILV or LP * from the contract balance. * * @param _stakeId stake ID to unstake from, zero-indexed * @param _value value of tokens to unstake */ function unstake(uint256 _stakeId, uint256 _value) external virtual { // checks if the contract is in a paused state _requireNotPaused(); // we're using selector to simplify input and state validation bytes4 fnSelector = this.unstake.selector; // verify a value is set fnSelector.verifyNonZeroInput(_value, 0); // get a link to user data struct, we will write to it later User storage user = users[msg.sender]; // get a link to the corresponding stake, we may write to it later Stake.Data storage userStake = user.stakes[_stakeId]; // checks if stake is unlocked already fnSelector.verifyState(_now256() > userStake.lockedUntil, 0); // stake structure may get deleted, so we save isYield flag to be able to use it // we also save stakeValue for gasSavings (uint120 stakeValue, bool isYield) = (userStake.value, userStake.isYield); // verify available balance fnSelector.verifyInput(stakeValue >= _value, 1); // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(msg.sender); // and process current pending rewards if any _updateReward(msg.sender, v1WeightToAdd); // store stake weight uint256 previousWeight = userStake.weight(); // value used to save new weight after updates in storage uint256 newWeight; // update the stake, or delete it if its depleted if (stakeValue - _value == 0) { // deletes stake struct, no need to save new weight because it stays 0 delete user.stakes[_stakeId]; } else { userStake.value -= (_value).toUint120(); // saves new weight to memory newWeight = userStake.weight(); } // update user record user.totalWeight = uint248(user.totalWeight - previousWeight + newWeight); // update global weight variable globalWeight = globalWeight - previousWeight + newWeight; // update global pool token count poolTokenReserve -= _value; // if the stake was created by the pool itself as a yield reward if (isYield) { // mint the yield via the factory _factory.mintYieldTo(msg.sender, _value, false); } else { // otherwise just return tokens back to holder IERC20Upgradeable(poolToken).safeTransfer(msg.sender, _value); } // emits an event emit LogUnstakeLocked(msg.sender, _stakeId, _value, isYield); } /** * @dev Executes unstake on multiple stakeIds. See `unstakeLocked()`. * @dev Optimizes gas by requiring all unstakes to be made either in yield stakes * or in non yield stakes. That way we can transfer or mint tokens in one call. * * @notice User is required to either mint ILV or unstake pool tokens in the function call. * There's no way to do both operations in one call. * * @param _stakes array of stakeIds and values to be unstaked in each stake from * the msg.sender * @param _unstakingYield whether all stakeIds have isYield flag set to true or false, * i.e if we're minting ILV or transferring pool tokens */ function unstakeMultiple(UnstakeParameter[] calldata _stakes, bool _unstakingYield) external virtual { // checks if the contract is in a paused state _requireNotPaused(); // we're using selector to simplify input and state validation bytes4 fnSelector = this.unstakeMultiple.selector; // verifies if user has passed any value to be unstaked fnSelector.verifyNonZeroInput(_stakes.length, 0); // gets storage pointer to the user User storage user = users[msg.sender]; // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(msg.sender); _updateReward(msg.sender, v1WeightToAdd); // initialize variables that expect to receive the total // weight to be removed from the user and the value to be // unstaked from the pool. uint256 weightToRemove; uint256 valueToUnstake; for (uint256 i = 0; i < _stakes.length; i++) { // destructure calldata parameters (uint256 _stakeId, uint256 _value) = (_stakes[i].stakeId, _stakes[i].value); Stake.Data storage userStake = user.stakes[_stakeId]; // checks if stake is unlocked already fnSelector.verifyState(_now256() > userStake.lockedUntil, i * 3); // checks if unstaking value is valid fnSelector.verifyNonZeroInput(_value, 1); // stake structure may get deleted, so we save isYield flag to be able to use it // we also save stakeValue for gas savings (uint120 stakeValue, bool isYield) = (userStake.value, userStake.isYield); // verifies if the selected stake is yield (i.e ILV to be minted) // or not, the function needs to either mint yield or transfer tokens // and can't do both operations at the same time. fnSelector.verifyState(isYield == _unstakingYield, i * 3 + 1); // checks if there's enough tokens to unstake fnSelector.verifyState(stakeValue >= _value, i * 3 + 2); // store stake weight uint256 previousWeight = userStake.weight(); // value used to save new weight after updates in storage uint256 newWeight; // update the stake, or delete it if its depleted if (stakeValue - _value == 0) { // deletes stake struct, no need to save new weight because it stays 0 delete user.stakes[_stakeId]; } else { // removes _value from the stake with safe cast userStake.value -= (_value).toUint120(); // saves new weight to memory newWeight = userStake.weight(); } // updates the values initialized earlier with the amounts that // need to be subtracted (weight) and transferred (value to unstake) weightToRemove += previousWeight - newWeight; valueToUnstake += _value; } // subtracts weight user.totalWeight -= (weightToRemove).toUint248(); // update global variable globalWeight -= weightToRemove; // update pool token count poolTokenReserve -= valueToUnstake; // if the stake was created by the pool itself as a yield reward if (_unstakingYield) { // mint the yield via the factory _factory.mintYieldTo(msg.sender, valueToUnstake, false); } else { // otherwise just return tokens back to holder IERC20Upgradeable(poolToken).safeTransfer(msg.sender, valueToUnstake); } // emits an event emit LogUnstakeLockedMultiple(msg.sender, valueToUnstake, _unstakingYield); } /** * @dev Used internally, mostly by children implementations, see `sync()`. * * @dev Updates smart contract state (`yieldRewardsPerWeight`, `lastYieldDistribution`), * updates factory state via `updateILVPerSecond` */ function _sync() internal virtual { // gas savings IFactory factory_ = _factory; // update ILV per second value in factory if required if (factory_.shouldUpdateRatio()) { factory_.updateILVPerSecond(); } // check bound conditions and if these are not met - // exit silently, without emitting an event uint256 endTime = factory_.endTime(); if (lastYieldDistribution >= endTime) { return; } if (_now256() <= lastYieldDistribution) { return; } // if locking weight is zero - update only `lastYieldDistribution` and exit if (globalWeight == 0 && v1GlobalWeight == 0) { lastYieldDistribution = (_now256()).toUint64(); return; } // to calculate the reward we need to know how many seconds passed, and reward per second uint256 currentTimestamp = _now256() > endTime ? endTime : _now256(); uint256 secondsPassed = currentTimestamp - lastYieldDistribution; uint256 ilvPerSecond = factory_.ilvPerSecond(); // calculate the reward uint256 ilvReward = (secondsPassed * ilvPerSecond * weight) / factory_.totalWeight(); // update rewards per weight and `lastYieldDistribution` yieldRewardsPerWeight += ilvReward.getRewardPerWeight((globalWeight + v1GlobalWeight)); lastYieldDistribution = (currentTimestamp).toUint64(); // emit an event emit LogSync(msg.sender, yieldRewardsPerWeight, lastYieldDistribution); } /** * @dev claims all pendingYield from _staker using ILV or sILV. * * @notice sILV is minted straight away to _staker wallet, ILV is created as * a new stake and locked for Stake.MAX_STAKE_PERIOD. * * @param _staker user address * @param _useSILV whether the user wants to claim ILV or sILV */ function _claimYieldRewards(address _staker, bool _useSILV) internal virtual { // get link to a user data structure, we will write into it later User storage user = users[_staker]; // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(_staker); // update user state _updateReward(_staker, v1WeightToAdd); // check pending yield rewards to claim and save to memory uint256 pendingYieldToClaim = uint256(user.pendingYield); // if pending yield is zero - just return silently if (pendingYieldToClaim == 0) return; // clears user pending yield user.pendingYield = 0; // if sILV is requested if (_useSILV) { // - mint sILV _factory.mintYieldTo(_staker, pendingYieldToClaim, true); } else if (poolToken == _ilv) { // calculate pending yield weight, // 2e6 is the bonus weight when staking for 1 year uint256 stakeWeight = pendingYieldToClaim * Stake.YIELD_STAKE_WEIGHT_MULTIPLIER; // if the pool is ILV Pool - create new ILV stake // and save it - push it into stakes array Stake.Data memory newStake = Stake.Data({ value: (pendingYieldToClaim).toUint120(), lockedFrom: (_now256()).toUint64(), lockedUntil: (_now256() + Stake.MAX_STAKE_PERIOD).toUint64(), // staking yield for 1 year isYield: true }); // add memory stake to storage user.stakes.push(newStake); // updates total user weight with the newly created stake's weight user.totalWeight += (stakeWeight).toUint248(); // update global variable globalWeight += stakeWeight; // update reserve count poolTokenReserve += pendingYieldToClaim; } else { // for other pools - stake as pool address ilvPool = _factory.getPoolAddress(_ilv); IILVPool(ilvPool).stakeAsPool(_staker, pendingYieldToClaim); } // emits an event emit LogClaimYieldRewards(msg.sender, _staker, _useSILV, pendingYieldToClaim); } /** * @dev Claims all pendingRevDis from _staker using ILV. * @dev ILV is sent straight away to _staker address. * * @param _staker user address */ function _claimVaultRewards(address _staker) internal virtual { // get link to a user data structure, we will write into it later User storage user = users[_staker]; // uses v1 weight values for rewards calculations uint256 v1WeightToAdd = _useV1Weight(_staker); // update user state _updateReward(_staker, v1WeightToAdd); // check pending yield rewards to claim and save to memory uint256 pendingRevDis = uint256(user.pendingRevDis); // if pending yield is zero - just return silently if (pendingRevDis == 0) return; // clears user pending revenue distribution user.pendingRevDis = 0; IERC20Upgradeable(_ilv).safeTransfer(_staker, pendingRevDis); // emits an event emit LogClaimVaultRewards(msg.sender, _staker, pendingRevDis); } /** * @dev Calls CorePoolV1 contract, gets v1 stake ids weight and returns. * @dev Used by `_pendingRewards()` to calculate yield and revenue distribution * rewards taking v1 weights into account. * * @notice If v1 weights have changed since last call, we use latest v1 weight for * yield and revenue distribution rewards calculations, and recalculate * user sub rewards values in order to have correct rewards estimations. * * @param _staker user address passed * * @return totalV1Weight uint256 value of v1StakesIds weights */ function _useV1Weight(address _staker) internal virtual returns (uint256 totalV1Weight) { // gets user storage pointer User storage user = users[_staker]; // gas savings uint256 v1StakesLength = user.v1IdsLength; // checks if user has any migrated stake from v1 if (v1StakesLength > 0) { // loops through v1StakesIds and adds v1 weight for (uint256 i = 0; i < v1StakesLength; i++) { // saves v1 stake id to memory uint256 stakeId = user.v1StakesIds[i]; (, uint256 _weight, , , ) = ICorePoolV1(corePoolV1).getDeposit(_staker, stakeId); // gets weight stored initially in the v1StakesWeights mapping // through V2Migrator contract uint256 storedWeight = v1StakesWeights[_staker][stakeId]; // only stores the current v1 weight that is going to be used for calculations // if current v1 weight is equal to or less than the stored weight. // This way we make sure that v1 weight never increases for any reason // (e.g increasing a v1 stake lock through v1 contract) and messes up calculations. totalV1Weight += _weight <= storedWeight ? _weight : storedWeight; // if _weight has updated in v1 to a lower value, we also update // stored weight in v2 for next calculations if (storedWeight > _weight) { // if deposit has been completely unstaked in v1, set stake id weight to 1 // so we can keep track that it has been already migrated. // otherwise just update value to _weight v1StakesWeights[_staker][stakeId] = _weight == 0 ? 1 : _weight; } } } } /** * @dev Checks if pool is paused. * @dev We use this internal function instead of the modifier coming from * Pausable contract in order to decrease contract's bytecode size. */ function _requireNotPaused() internal view virtual { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("_requireNotPaused()"))` bytes4 fnSelector = 0xabb87a6f; // checks paused variable value from Pausable Open Zeppelin fnSelector.verifyState(!paused(), 0); } /** * @dev Must be called every time user.totalWeight is changed. * @dev Syncs the global pool state, processes the user pending rewards (if any), * and updates check points values stored in the user struct. * @dev If user is coming from v1 pool, it expects to receive this v1 user weight * to include in rewards calculations. * * @param _staker user address * @param _v1WeightToAdd v1 weight to be added to calculations */ function _updateReward(address _staker, uint256 _v1WeightToAdd) internal virtual { // update pool state _sync(); // gets storage reference to the user User storage user = users[_staker]; // gas savings uint256 userTotalWeight = uint256(user.totalWeight) + _v1WeightToAdd; // calculates pending yield to be added uint256 pendingYield = userTotalWeight.earned(yieldRewardsPerWeight, user.yieldRewardsPerWeightPaid); // calculates pending reenue distribution to be added uint256 pendingRevDis = userTotalWeight.earned(vaultRewardsPerWeight, user.vaultRewardsPerWeightPaid); // increases stored user.pendingYield with value returned user.pendingYield += pendingYield.toUint128(); // increases stored user.pendingRevDis with value returned user.pendingRevDis += pendingRevDis.toUint128(); // updates user checkpoint values for future calculations user.yieldRewardsPerWeightPaid = yieldRewardsPerWeight; user.vaultRewardsPerWeightPaid = vaultRewardsPerWeight; // emit an event emit LogUpdateRewards(msg.sender, _staker, pendingYield, pendingRevDis); } /** * @dev See UUPSUpgradeable `_authorizeUpgrade()`. * @dev Just checks if `msg.sender` == `factory.owner()` i.e eDAO multisig address. * @dev eDAO multisig is responsible by handling upgrades and executing other * admin actions approved by the Council. */ function _authorizeUpgrade(address) internal view virtual override { // checks caller is factory.owner() _requireIsFactoryController(); } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[39] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; /** * @title Errors Library. * * @notice Introduces some very common input and state validation for smart contracts, * such as non-zero input validation, general boolean expression validation, access validation. * * @notice Throws pre-defined errors instead of string error messages to reduce gas costs. * * @notice Since the library handles only very common errors, concrete smart contracts may * also introduce their own error types and handling. * * @author Basil Gorin */ library ErrorHandler { /** * @notice Thrown on zero input at index specified in a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param paramIndex function parameter index which caused an error thrown */ error ZeroInput(bytes4 fnSelector, uint8 paramIndex); /** * @notice Thrown on invalid input at index specified in a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param paramIndex function parameter index which caused an error thrown */ error InvalidInput(bytes4 fnSelector, uint8 paramIndex); /** * @notice Thrown on invalid state in a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param errorCode unique error code determining the exact place in code where error was thrown */ error InvalidState(bytes4 fnSelector, uint256 errorCode); /** * @notice Thrown on invalid access to a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param addr an address which access was denied, usually transaction sender */ error AccessDenied(bytes4 fnSelector, address addr); /** * @notice Verifies an input is set (non-zero). * * @param fnSelector function selector, defines a function which called the verification * @param value a value to check if it's set (non-zero) * @param paramIndex function parameter index which is verified */ function verifyNonZeroInput( bytes4 fnSelector, uint256 value, uint8 paramIndex ) internal pure { if (value == 0) { revert ZeroInput(fnSelector, paramIndex); } } /** * @notice Verifies an input is correct. * * @param fnSelector function selector, defines a function which called the verification * @param expr a boolean expression used to verify the input * @param paramIndex function parameter index which is verified */ function verifyInput( bytes4 fnSelector, bool expr, uint8 paramIndex ) internal pure { if (!expr) { revert InvalidInput(fnSelector, paramIndex); } } /** * @notice Verifies smart contract state is correct. * * @param fnSelector function selector, defines a function which called the verification * @param expr a boolean expression used to verify the contract state * @param errorCode unique error code determining the exact place in code which is verified */ function verifyState( bytes4 fnSelector, bool expr, uint256 errorCode ) internal pure { if (!expr) { revert InvalidState(fnSelector, errorCode); } } /** * @notice Verifies an access to the function. * * @param fnSelector function selector, defines a function which called the verification * @param expr a boolean expression used to verify the access */ function verifyAccess(bytes4 fnSelector, bool expr) internal view { if (!expr) { revert AccessDenied(fnSelector, msg.sender); } } } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; /** * @dev Stake library used by ILV pool and Sushi LP Pool. * * @dev Responsible to manage weight calculation and store important constants * related to stake period, base weight and multipliers utilized. */ library Stake { struct Data { /// @dev token amount staked uint120 value; /// @dev locking period - from uint64 lockedFrom; /// @dev locking period - until uint64 lockedUntil; /// @dev indicates if the stake was created as a yield reward bool isYield; } /** * @dev Stake weight is proportional to stake value and time locked, precisely * "stake value wei multiplied by (fraction of the year locked plus one)". * @dev To avoid significant precision loss due to multiplication by "fraction of the year" [0, 1], * weight is stored multiplied by 1e6 constant, as an integer. * @dev Corner case 1: if time locked is zero, weight is stake value multiplied by 1e6 + base weight * @dev Corner case 2: if time locked is two years, division of (lockedUntil - lockedFrom) / MAX_STAKE_PERIOD is 1e6, and * weight is a stake value multiplied by 2 * 1e6. */ uint256 internal constant WEIGHT_MULTIPLIER = 1e6; /** * @dev Minimum weight value, if result of multiplication using WEIGHT_MULTIPLIER * is 0 (e.g stake flexible), then BASE_WEIGHT is used. */ uint256 internal constant BASE_WEIGHT = 1e6; /** * @dev Minimum period that someone can lock a stake for. */ uint256 internal constant MIN_STAKE_PERIOD = 30 days; /** * @dev Maximum period that someone can lock a stake for. */ uint256 internal constant MAX_STAKE_PERIOD = 365 days; /** * @dev Rewards per weight are stored multiplied by 1e20 as uint. */ uint256 internal constant REWARD_PER_WEIGHT_MULTIPLIER = 1e20; /** * @dev When we know beforehand that staking is done for yield instead of * executing `weight()` function we use the following constant. */ uint256 internal constant YIELD_STAKE_WEIGHT_MULTIPLIER = 2 * 1e6; function weight(Data storage _self) internal view returns (uint256) { return uint256( (((_self.lockedUntil - _self.lockedFrom) * WEIGHT_MULTIPLIER) / MAX_STAKE_PERIOD + BASE_WEIGHT) * _self.value ); } /** * @dev Converts stake weight (not to be mixed with the pool weight) to * ILV reward value, applying the 10^12 division on weight * * @param _weight stake weight * @param _rewardPerWeight ILV reward per weight * @param _rewardPerWeightPaid last reward per weight value used for user earnings * @return reward value normalized to 10^12 */ function earned( uint256 _weight, uint256 _rewardPerWeight, uint256 _rewardPerWeightPaid ) internal pure returns (uint256) { // apply the formula and return return (_weight * (_rewardPerWeight - _rewardPerWeightPaid)) / REWARD_PER_WEIGHT_MULTIPLIER; } /** * @dev Converts reward ILV value to stake weight (not to be mixed with the pool weight), * applying the 10^12 multiplication on the reward. * - OR - * @dev Converts reward ILV value to reward/weight if stake weight is supplied as second * function parameter instead of reward/weight. * * @param _reward yield reward * @param _globalWeight total weight in the pool * @return reward per weight value */ function getRewardPerWeight(uint256 _reward, uint256 _globalWeight) internal pure returns (uint256) { // apply the reverse formula and return return (_reward * REWARD_PER_WEIGHT_MULTIPLIER) / _globalWeight; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { ICorePool } from "./ICorePool.sol"; interface IFactory { function owner() external view returns (address); function ilvPerSecond() external view returns (uint192); function totalWeight() external view returns (uint32); function secondsPerUpdate() external view returns (uint32); function endTime() external view returns (uint32); function lastRatioUpdate() external view returns (uint32); function pools(address _poolToken) external view returns (ICorePool); function poolExists(address _poolAddress) external view returns (bool); function getPoolAddress(address poolToken) external view returns (address); function getPoolData(address _poolToken) external view returns ( address, address, uint32, bool ); function shouldUpdateRatio() external view returns (bool); function registerPool(ICorePool pool) external; function updateILVPerSecond() external; function mintYieldTo( address _to, uint256 _value, bool _useSILV ) external; function changePoolWeight(address pool, uint32 weight) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Stake } from "../libraries/Stake.sol"; interface ICorePool { function users(address _user) external view returns ( uint128, uint128, uint128, uint248, uint8, uint256, uint256 ); function poolToken() external view returns (address); function isFlashPool() external view returns (bool); function weight() external view returns (uint32); function lastYieldDistribution() external view returns (uint64); function yieldRewardsPerWeight() external view returns (uint256); function globalWeight() external view returns (uint256); function pendingRewards(address _user) external view returns (uint256, uint256); function poolTokenReserve() external view returns (uint256); function balanceOf(address _user) external view returns (uint256); function getTotalReserves() external view returns (uint256); function getStake(address _user, uint256 _stakeId) external view returns (Stake.Data memory); function getStakesLength(address _user) external view returns (uint256); function sync() external; function setWeight(uint32 _weight) external; function receiveVaultRewards(uint256 value) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; interface ICorePoolV1 { struct V1Stake { // @dev token amount staked uint256 tokenAmount; // @dev stake weight uint256 weight; // @dev locking period - from uint64 lockedFrom; // @dev locking period - until uint64 lockedUntil; // @dev indicates if the stake was created as a yield reward bool isYield; } struct V1User { // @dev Total staked amount uint256 tokenAmount; // @dev Total weight uint256 totalWeight; // @dev Auxiliary variable for yield calculation uint256 subYieldRewards; // @dev Auxiliary variable for vault rewards calculation uint256 subVaultRewards; // @dev An array of holder's deposits V1Stake[] deposits; } function users(address _who) external view returns ( uint256, uint256, uint256, uint256 ); function getDeposit(address _from, uint256 _stakeId) external view returns ( uint256, uint256, uint64, uint64, bool ); function poolToken() external view returns (address); function usersLockingWeight() external view returns (uint256); function poolTokenReserve() external view returns (uint256); } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { V2Migrator } from "./base/V2Migrator.sol"; import { CorePool } from "./base/CorePool.sol"; import { ErrorHandler } from "./libraries/ErrorHandler.sol"; import { ICorePoolV1 } from "./interfaces/ICorePoolV1.sol"; /** * @title The Sushi LP Pool. * * @dev Extends all functionality from V2Migrator contract, there isn't a lot of * additions compared to ILV pool. Sushi LP pool basically needs to be able * to be called by ILV pool in batch calls where we claim rewards from multiple * pools. */ contract SushiLPPool is Initializable, V2Migrator { using ErrorHandler for bytes4; /// @dev Calls __V2Migrator_init(). function initialize( address ilv_, address silv_, address _poolToken, address _factory, uint64 _initTime, uint32 _weight, address _corePoolV1, uint256 v1StakeMaxPeriod_ ) external initializer { __V2Migrator_init(ilv_, silv_, _poolToken, _corePoolV1, _factory, _initTime, _weight, v1StakeMaxPeriod_); } /// @inheritdoc CorePool function getTotalReserves() external view virtual override returns (uint256 totalReserves) { totalReserves = poolTokenReserve + ICorePoolV1(corePoolV1).poolTokenReserve(); } /** * @notice This function can be called only by ILV core pool. * * @dev Uses ILV pool as a router by receiving the _staker address and executing * the internal `_claimYieldRewards()`. * @dev Its usage allows claiming multiple pool contracts in one transaction. * * @param _staker user address * @param _useSILV whether it should claim pendingYield as ILV or sILV */ function claimYieldRewardsFromRouter(address _staker, bool _useSILV) external virtual { // checks if contract is paused _requireNotPaused(); // checks if caller is the ILV pool _requirePoolIsValid(); // calls internal _claimYieldRewards function (in CorePool.sol) _claimYieldRewards(_staker, _useSILV); } /** * @notice This function can be called only by ILV core pool. * * @dev Uses ILV pool as a router by receiving the _staker address and executing * the internal `_claimVaultRewards()`. * @dev Its usage allows claiming multiple pool contracts in one transaction. * * @param _staker user address */ function claimVaultRewardsFromRouter(address _staker) external virtual { // checks if contract is paused _requireNotPaused(); // checks if caller is the ILV pool _requirePoolIsValid(); // calls internal _claimVaultRewards function (in CorePool.sol) _claimVaultRewards(_staker); } /** * @dev Checks if caller is ILV pool. * @dev We are using an internal function instead of a modifier in order to * reduce the contract's bytecode size. */ function _requirePoolIsValid() internal view virtual { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("_requirePoolIsValid()"))` bytes4 fnSelector = 0x250f303f; // checks if pool is the ILV pool bool poolIsValid = address(_factory.pools(_ilv)) == msg.sender; fnSelector.verifyState(poolIsValid, 0); } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Collection of functions related to the address type */ library AddressUpgradeable { /** * @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; assembly { size := extcodesize(account) } return size > 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://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, "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 functionCall(target, data, "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"); require(isContract(target), "Address: call to non-contract"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResult(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) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason 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 { // 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 assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../ERC1967/ERC1967UpgradeUpgradeable.sol"; import "./Initializable.sol"; /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. * * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing * `UUPSUpgradeable` with a custom implementation of upgrades. * * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. * * _Available since v4.1._ */ abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable { function __UUPSUpgradeable_init() internal initializer { __ERC1967Upgrade_init_unchained(); __UUPSUpgradeable_init_unchained(); } function __UUPSUpgradeable_init_unchained() internal initializer { } /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment address private immutable __self = address(this); /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to * fail. */ modifier onlyProxy() { require(address(this) != __self, "Function must be called through delegatecall"); require(_getImplementation() == __self, "Function must be called through active proxy"); _; } /** * @dev Upgrade the implementation of the proxy to `newImplementation`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. */ function upgradeTo(address newImplementation) external virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, new bytes(0), false); } /** * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call * encoded in `data`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. */ function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, data, true); } /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by * {upgradeTo} and {upgradeToAndCall}. * * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity * function _authorizeUpgrade(address) internal override onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../proxy/utils/Initializable.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 ReentrancyGuardUpgradeable is Initializable { // 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; function __ReentrancyGuard_init() internal initializer { __ReentrancyGuard_init_unchained(); } function __ReentrancyGuard_init_unchained() internal initializer { _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() { // On the first call to nonReentrant, _notEntered will be true require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } uint256[49] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../utils/ContextUpgradeable.sol"; import "../proxy/utils/Initializable.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract PausableUpgradeable is Initializable, ContextUpgradeable { /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); bool private _paused; /** * @dev Initializes the contract in unpaused state. */ function __Pausable_init() internal initializer { __Context_init_unchained(); __Pausable_init_unchained(); } function __Pausable_init_unchained() internal initializer { _paused = false; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { require(!paused(), "Pausable: paused"); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { require(paused(), "Pausable: not paused"); _; } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } uint256[49] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; /// @title Function for getting block timestamp. /// @dev Base contract that is overridden for tests. abstract contract Timestamp { /** * @dev Testing time-dependent functionality is difficult and the best way of * doing it is to override time in helper test smart contracts. * * @return `block.timestamp` in mainnet, custom values in testnets (if overridden). */ function _now256() internal view virtual returns (uint256) { // return current block timestamp return block.timestamp; } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { FactoryControlled } from "./FactoryControlled.sol"; import { ErrorHandler } from "../libraries/ErrorHandler.sol"; abstract contract VaultRecipient is Initializable, FactoryControlled { using ErrorHandler for bytes4; /// @dev Link to deployed IlluviumVault instance. address internal _vault; /// @dev Used to calculate vault rewards. /// @dev This value is different from "reward per token" used in locked pool. /// @dev Note: stakes are different in duration and "weight" reflects that, uint256 public vaultRewardsPerWeight; /** * @dev Fired in `setVault()`. * * @param by an address which executed the function, always a factory owner * @param previousVault previous vault contract address * @param newVault new vault address */ event LogSetVault(address indexed by, address previousVault, address newVault); /** * @dev Executed only by the factory owner to Set the vault. * * @param vault_ an address of deployed IlluviumVault instance */ function setVault(address vault_) external virtual { // we're using selector to simplify input and state validation bytes4 fnSelector = this.setVault.selector; // verify function is executed by the factory owner fnSelector.verifyState(_factory.owner() == msg.sender, 0); // verify input is set fnSelector.verifyInput(vault_ != address(0), 0); // saves current vault to memory address previousVault = vault_; // update vault address _vault = vault_; // emit an event emit LogSetVault(msg.sender, previousVault, _vault); } /// @dev Utility function to check if caller is the Vault contract function _requireIsVault() internal view virtual { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("_requireIsVault()"))` bytes4 fnSelector = 0xeeea774b; // checks if caller is the same stored vault address fnSelector.verifyAccess(msg.sender == _vault); } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[48] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { ICorePool } from "./ICorePool.sol"; interface IILVPool is ICorePool { function stakeAsPool(address _staker, uint256 _value) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.2; import "../beacon/IBeaconUpgradeable.sol"; import "../../utils/AddressUpgradeable.sol"; import "../../utils/StorageSlotUpgradeable.sol"; import "../utils/Initializable.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. * * _Available since v4.1._ * * @custom:oz-upgrades-unsafe-allow delegatecall */ abstract contract ERC1967UpgradeUpgradeable is Initializable { function __ERC1967Upgrade_init() internal initializer { __ERC1967Upgrade_init_unchained(); } function __ERC1967Upgrade_init_unchained() internal initializer { } // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Returns the current implementation address. */ function _getImplementation() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * * Emits an {Upgraded} event. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Perform implementation upgrade with additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCall( address newImplementation, bytes memory data, bool forceCall ) internal { _upgradeTo(newImplementation); if (data.length > 0 || forceCall) { _functionDelegateCall(newImplementation, data); } } /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCallSecure( address newImplementation, bytes memory data, bool forceCall ) internal { address oldImplementation = _getImplementation(); // Initial upgrade and setup call _setImplementation(newImplementation); if (data.length > 0 || forceCall) { _functionDelegateCall(newImplementation, data); } // Perform rollback test if not already in progress StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT); if (!rollbackTesting.value) { // Trigger rollback using upgradeTo from the new implementation rollbackTesting.value = true; _functionDelegateCall( newImplementation, abi.encodeWithSignature("upgradeTo(address)", oldImplementation) ); rollbackTesting.value = false; // Check rollback was effective require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); // Finally reset to the new implementation and log the upgrade _upgradeTo(newImplementation); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { require(newAdmin != address(0), "ERC1967: new admin is the zero address"); StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. */ function _changeAdmin(address newAdmin) internal { emit AdminChanged(_getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Emitted when the beacon is upgraded. */ event BeaconUpgraded(address indexed beacon); /** * @dev Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract"); require( AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon; } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * * Emits a {BeaconUpgraded} event. */ function _upgradeBeaconToAndCall( address newBeacon, bytes memory data, bool forceCall ) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { _functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data); } } /** * @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) private returns (bytes memory) { require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed"); } uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeaconUpgradeable { /** * @dev Must return an address that can be used as a delegate call target. * * {BeaconProxy} will check that this address is a contract. */ function implementation() external view returns (address); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlotUpgradeable { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { assembly { r.slot := slot } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../proxy/utils/Initializable.sol"; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract ContextUpgradeable is Initializable { function __Context_init() internal initializer { __Context_init_unchained(); } function __Context_init_unchained() internal initializer { } function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { IFactory } from "../interfaces/IFactory.sol"; import { ErrorHandler } from "../libraries/ErrorHandler.sol"; /** * @title FactoryControlled * * @dev Abstract smart contract responsible to hold IFactory factory address. * @dev Stores PoolFactory address on initialization. * */ abstract contract FactoryControlled is Initializable { using ErrorHandler for bytes4; /// @dev Link to the pool factory IlluviumPoolFactory instance. IFactory internal _factory; /// @dev Attachs PoolFactory address to the FactoryControlled contract. function __FactoryControlled_init(address factory_) internal initializer { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("__FactoryControlled_init(address)"))` bytes4 fnSelector = 0xbb6c0dbf; fnSelector.verifyNonZeroInput(uint160(factory_), 0); _factory = IFactory(factory_); } /// @dev checks if caller is factory admin (eDAO multisig address). function _requireIsFactoryController() internal view virtual { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("_requireIsFactoryController()"))` bytes4 fnSelector = 0x39e71deb; fnSelector.verifyAccess(msg.sender == _factory.owner()); } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; }
File 4 of 5: ERC1967Proxy
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to * be specified by overriding the virtual {_implementation} function. * * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a * different contract through the {_delegate} function. * * The success and return data of the delegated call will be returned back to the caller of the proxy. */ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * * This function does not return to its internall call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { // solhint-disable-next-line no-inline-assembly assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /** * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internall call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); _delegate(_implementation()); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other * function in the contract matches the call data. */ fallback () external payable virtual { _fallback(); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * is empty. */ receive () external payable virtual { _fallback(); } /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. * * If overriden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual { } } /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. * * _Available since v4.1._ * */ abstract contract ERC1967Upgrade { // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Returns the current implementation address. */ function _getImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * * Emits an {Upgraded} event. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Perform implementation upgrade with additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } } /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal { address oldImplementation = _getImplementation(); // Initial upgrade and setup call _setImplementation(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } // Perform rollback test if not already in progress StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT); if (!rollbackTesting.value) { // Trigger rollback using upgradeTo from the new implementation rollbackTesting.value = true; Address.functionDelegateCall( newImplementation, abi.encodeWithSignature( "upgradeTo(address)", oldImplementation ) ); rollbackTesting.value = false; // Check rollback was effective require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); // Finally reset to the new implementation and log the upgrade _setImplementation(newImplementation); emit Upgraded(newImplementation); } } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * * Emits a {BeaconUpgraded} event. */ function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { require(newAdmin != address(0), "ERC1967: new admin is the zero address"); StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. */ function _changeAdmin(address newAdmin) internal { emit AdminChanged(_getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Emitted when the beacon is upgraded. */ event BeaconUpgraded(address indexed beacon); /** * @dev Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlot.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require( Address.isContract(newBeacon), "ERC1967: new beacon is not a contract" ); require( Address.isContract(IBeacon(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; } } /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeacon { /** * @dev Must return an address that can be used as a delegate call target. * * {BeaconProxy} will check that this address is a contract. */ function implementation() external view returns (address); } /** * @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; } /** * @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, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (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 functionCall(target, data, "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"); require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: value }(data); return _verifyCallResult(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) { require(isContract(target), "Address: static call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.staticcall(data); return _verifyCallResult(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) { require(isContract(target), "Address: delegate call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return _verifyCallResult(success, returndata, errorMessage); } function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { if (success) { return returndata; } else { // 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(errorMessage); } } } } /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { assembly { r.slot := slot } } } /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } /** * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. */ contract ProxyAdmin is Ownable { /** * @dev Returns the current implementation of `proxy`. * * Requirements: * * - This contract must be the admin of `proxy`. */ function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("implementation()")) == 0x5c60da1b (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); require(success); return abi.decode(returndata, (address)); } /** * @dev Returns the current admin of `proxy`. * * Requirements: * * - This contract must be the admin of `proxy`. */ function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("admin()")) == 0xf851a440 (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); require(success); return abi.decode(returndata, (address)); } /** * @dev Changes the admin of `proxy` to `newAdmin`. * * Requirements: * * - This contract must be the current admin of `proxy`. */ function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner { proxy.changeAdmin(newAdmin); } /** * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. * * Requirements: * * - This contract must be the admin of `proxy`. */ function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner { proxy.upgradeTo(implementation); } /** * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See * {TransparentUpgradeableProxy-upgradeToAndCall}. * * Requirements: * * - This contract must be the admin of `proxy`. */ function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner { proxy.upgradeToAndCall{value: msg.value}(implementation, data); } } /** * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify * continuation of the upgradability. * * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism. * * _Available since v4.1._ */ abstract contract UUPSUpgradeable is ERC1967Upgrade { function upgradeTo(address newImplementation) external virtual { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, bytes(""), false); } function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, data, true); } function _authorizeUpgrade(address newImplementation) internal virtual; } abstract contract Proxiable is UUPSUpgradeable { function _authorizeUpgrade(address newImplementation) internal override { _beforeUpgrade(newImplementation); } function _beforeUpgrade(address newImplementation) internal virtual; } contract ChildOfProxiable is Proxiable { function _beforeUpgrade(address newImplementation) internal virtual override {} } /** * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an * implementation address that can be changed. This address is stored in storage in the location specified by * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the * implementation behind the proxy. */ contract ERC1967Proxy is Proxy, ERC1967Upgrade { /** * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded * function call, and allows initializating the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); _upgradeToAndCall(_logic, _data, false); } /** * @dev Returns the current implementation address. */ function _implementation() internal view virtual override returns (address impl) { return ERC1967Upgrade._getImplementation(); } } /** * @dev This contract implements a proxy that is upgradeable by an admin. * * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector * clashing], which can potentially be used in an attack, this contract uses the * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two * things that go hand in hand: * * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if * that call matches one of the admin functions exposed by the proxy itself. * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the * implementation. If the admin tries to call a function on the implementation it will fail with an error that says * "admin cannot fallback to proxy target". * * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due * to sudden errors when trying to call a function from the proxy implementation. * * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. */ contract TransparentUpgradeableProxy is ERC1967Proxy { /** * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); _changeAdmin(admin_); } /** * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. */ modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } /** * @dev Returns the current admin. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ function admin() external ifAdmin returns (address admin_) { admin_ = _getAdmin(); } /** * @dev Returns the current implementation. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ function implementation() external ifAdmin returns (address implementation_) { implementation_ = _implementation(); } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. * * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. */ function changeAdmin(address newAdmin) external virtual ifAdmin { _changeAdmin(newAdmin); } /** * @dev Upgrade the implementation of the proxy. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. */ function upgradeTo(address newImplementation) external ifAdmin { _upgradeToAndCall(newImplementation, bytes(""), false); } /** * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the * proxied contract. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. */ function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { _upgradeToAndCall(newImplementation, data, true); } /** * @dev Returns the current admin. */ function _admin() internal view virtual returns (address) { return _getAdmin(); } /** * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. */ function _beforeFallback() internal virtual override { require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target"); super._beforeFallback(); } } // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins. contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy { constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {} }
File 5 of 5: PoolFactory
// SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { SafeCast } from "./libraries/SafeCast.sol"; import { Timestamp } from "./base/Timestamp.sol"; import { ICorePool } from "./interfaces/ICorePool.sol"; import { IERC20Mintable } from "./interfaces/IERC20Mintable.sol"; import { ErrorHandler } from "./libraries/ErrorHandler.sol"; /** * @title Pool Factory V2 * * @dev Pool Factory manages Illuvium staking pools, provides a single * public interface to access the pools, provides an interface for the pools * to mint yield rewards, access pool-related info, update weights, etc. * * @dev The factory is authorized (via its owner) to register new pools, change weights * of the existing pools, removing the pools (by changing their weights to zero). * * @dev The factory requires ROLE_TOKEN_CREATOR permission on the ILV and sILV tokens to mint yield * (see `mintYieldTo` function). * * @notice The contract uses Ownable implementation, so only the eDAO is able to handle * admin activities, such as registering new pools, doing contract upgrades, * changing pool weights, managing emission schedules and so on. * */ contract PoolFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable, Timestamp { using ErrorHandler for bytes4; using SafeCast for uint256; /// @dev Auxiliary data structure used only in getPoolData() view function struct PoolData { // @dev pool token address (like ILV) address poolToken; // @dev pool address (like deployed core pool instance) address poolAddress; // @dev pool weight (200 for ILV pools, 800 for ILV/ETH pools - set during deployment) uint32 weight; // @dev flash pool flag bool isFlashPool; } /** * @dev ILV/second determines yield farming reward base * used by the yield pools controlled by the factory. */ uint192 public ilvPerSecond; /** * @dev The yield is distributed proportionally to pool weights; * total weight is here to help in determining the proportion. */ uint32 public totalWeight; /** * @dev ILV/second decreases by 3% every seconds/update * an update is triggered by executing `updateILVPerSecond` public function. */ uint32 public secondsPerUpdate; /** * @dev End time is the last timestamp when ILV/second can be decreased; * it is implied that yield farming stops after that timestamp. */ uint32 public endTime; /** * @dev Each time the ILV/second ratio gets updated, the timestamp * when the operation has occurred gets recorded into `lastRatioUpdate`. * @dev This timestamp is then used to check if seconds/update `secondsPerUpdate` * has passed when decreasing yield reward by 3%. */ uint32 public lastRatioUpdate; /// @dev ILV token address. address private _ilv; /// @dev sILV token address address private _silv; /// @dev Maps pool token address (like ILV) -> pool address (like core pool instance). mapping(address => address) public pools; /// @dev Keeps track of registered pool addresses, maps pool address -> exists flag. mapping(address => bool) public poolExists; /** * @dev Fired in registerPool() * * @param by an address which executed an action * @param poolToken pool token address (like ILV) * @param poolAddress deployed pool instance address * @param weight pool weight * @param isFlashPool flag indicating if pool is a flash pool */ event LogRegisterPool( address indexed by, address indexed poolToken, address indexed poolAddress, uint64 weight, bool isFlashPool ); /** * @dev Fired in `changePoolWeight()`. * * @param by an address which executed an action * @param poolAddress deployed pool instance address * @param weight new pool weight */ event LogChangePoolWeight(address indexed by, address indexed poolAddress, uint32 weight); /** * @dev Fired in `updateILVPerSecond()`. * * @param by an address which executed an action * @param newIlvPerSecond new ILV/second value */ event LogUpdateILVPerSecond(address indexed by, uint256 newIlvPerSecond); /** * @dev Fired in `setEndTime()`. * * @param by an address which executed the action * @param endTime new endTime value */ event LogSetEndTime(address indexed by, uint32 endTime); /** * @dev Initializes a factory instance * * @param ilv_ ILV ERC20 token address * @param silv_ sILV ERC20 token address * @param _ilvPerSecond initial ILV/second value for rewards * @param _secondsPerUpdate how frequently the rewards gets updated (decreased by 3%), seconds * @param _initTime timestamp to measure _secondsPerUpdate from * @param _endTime timestamp number when farming stops and rewards cannot be updated anymore */ function initialize( address ilv_, address silv_, uint192 _ilvPerSecond, uint32 _secondsPerUpdate, uint32 _initTime, uint32 _endTime ) external initializer { bytes4 fnSelector = this.initialize.selector; // verify the inputs are set correctly fnSelector.verifyNonZeroInput(uint160(ilv_), 0); fnSelector.verifyNonZeroInput(uint160(silv_), 1); fnSelector.verifyNonZeroInput(_ilvPerSecond, 2); fnSelector.verifyNonZeroInput(_secondsPerUpdate, 3); fnSelector.verifyNonZeroInput(_initTime, 4); fnSelector.verifyInput(_endTime > _now256(), 5); __Ownable_init(); // save the inputs into internal state variables _ilv = ilv_; _silv = silv_; ilvPerSecond = _ilvPerSecond; secondsPerUpdate = _secondsPerUpdate; lastRatioUpdate = _initTime; endTime = _endTime; } /** * @notice Given a pool token retrieves corresponding pool address. * * @dev A shortcut for `pools` mapping. * * @param poolToken pool token address (like ILV) to query pool address for * @return pool address for the token specified */ function getPoolAddress(address poolToken) external view virtual returns (address) { // read the mapping and return return address(pools[poolToken]); } /** * @notice Reads pool information for the pool defined by its pool token address, * designed to simplify integration with the front ends. * * @param _poolToken pool token address to query pool information for. * @return pool information packed in a PoolData struct. */ function getPoolData(address _poolToken) public view virtual returns (PoolData memory) { bytes4 fnSelector = this.getPoolData.selector; // get the pool address from the mapping ICorePool pool = ICorePool(pools[_poolToken]); // throw if there is no pool registered for the token specified fnSelector.verifyState(uint160(address(pool)) != 0, 0); // read pool information from the pool smart contract // via the pool interface (ICorePool) address poolToken = pool.poolToken(); bool isFlashPool = pool.isFlashPool(); uint32 weight = pool.weight(); // create the in-memory structure and return it return PoolData({ poolToken: poolToken, poolAddress: address(pool), weight: weight, isFlashPool: isFlashPool }); } /** * @dev Verifies if `secondsPerUpdate` has passed since last ILV/second * ratio update and if ILV/second reward can be decreased by 3%. * * @return true if enough time has passed and `updateILVPerSecond` can be executed. */ function shouldUpdateRatio() public view virtual returns (bool) { // if yield farming period has ended if (_now256() > endTime) { // ILV/second reward cannot be updated anymore return false; } // check if seconds/update have passed since last update return _now256() >= lastRatioUpdate + secondsPerUpdate; } /** * @dev Registers an already deployed pool instance within the factory. * * @dev Can be executed by the pool factory owner only. * * @param pool address of the already deployed pool instance */ function registerPool(address pool) public virtual onlyOwner { // read pool information from the pool smart contract // via the pool interface (ICorePool) address poolToken = ICorePool(pool).poolToken(); bool isFlashPool = ICorePool(pool).isFlashPool(); uint32 weight = ICorePool(pool).weight(); // create pool structure, register it within the factory pools[poolToken] = pool; poolExists[pool] = true; // update total pool weight of the factory totalWeight += weight; // emit an event emit LogRegisterPool(msg.sender, poolToken, address(pool), weight, isFlashPool); } /** * @notice Decreases ILV/second reward by 3%, can be executed * no more than once per `secondsPerUpdate` seconds. */ function updateILVPerSecond() external virtual { bytes4 fnSelector = this.updateILVPerSecond.selector; // checks if ratio can be updated i.e. if seconds/update have passed fnSelector.verifyState(shouldUpdateRatio(), 0); // decreases ILV/second reward by 3%. // To achieve that we multiply by 97 and then // divide by 100 ilvPerSecond = (ilvPerSecond * 97) / 100; // set current timestamp as the last ratio update timestamp lastRatioUpdate = (_now256()).toUint32(); // emit an event emit LogUpdateILVPerSecond(msg.sender, ilvPerSecond); } /** * @dev Mints ILV tokens; executed by ILV Pool only. * * @dev Requires factory to have ROLE_TOKEN_CREATOR permission * on the ILV ERC20 token instance. * * @param _to an address to mint tokens to * @param _value amount of ILV tokens to mint * @param _useSILV whether ILV or sILV should be minted */ function mintYieldTo( address _to, uint256 _value, bool _useSILV ) external virtual { bytes4 fnSelector = this.mintYieldTo.selector; // verify that sender is a pool registered withing the factory fnSelector.verifyState(poolExists[msg.sender], 0); // mints the requested token to the indicated address if (!_useSILV) { IERC20Mintable(_ilv).mint(_to, _value); } else { IERC20Mintable(_silv).mint(_to, _value); } } /** * @dev Changes the weight of the pool; * executed by the pool itself or by the factory owner. * * @param pool address of the pool to change weight for * @param weight new weight value to set to */ function changePoolWeight(address pool, uint32 weight) external virtual { bytes4 fnSelector = this.changePoolWeight.selector; // verify function is executed either by factory owner or by the pool itself fnSelector.verifyAccess(msg.sender == owner() || poolExists[msg.sender]); // recalculate total weight totalWeight = totalWeight + weight - ICorePool(pool).weight(); // set the new pool weight ICorePool(pool).setWeight(weight); // emit an event emit LogChangePoolWeight(msg.sender, address(pool), weight); } /** * @dev Updates yield generation ending timestamp. * * @param _endTime new end time value to be stored */ function setEndTime(uint32 _endTime) external virtual onlyOwner { bytes4 fnSelector = this.setEndTime.selector; // checks if _endTime is a timestap after the last time that // ILV/second has been updated fnSelector.verifyInput(_endTime > lastRatioUpdate, 0); // updates endTime state var endTime = _endTime; // emits an event emit LogSetEndTime(msg.sender, _endTime); } /** * @dev Overrides `Ownable.renounceOwnership()`, to avoid accidentally * renouncing ownership of the PoolFactory contract. */ function renounceOwnership() public virtual override {} /// @dev See `CorePool._authorizeUpgrade()` function _authorizeUpgrade(address) internal virtual override onlyOwner {} /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[45] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. */ abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. */ bool private _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool private _initializing; /** * @dev Modifier to protect an initializer function from being invoked twice. */ modifier initializer() { require(_initializing || !_initialized, "Initializable: contract is already initialized"); bool isTopLevelCall = !_initializing; if (isTopLevelCall) { _initializing = true; _initialized = true; } _; if (isTopLevelCall) { _initializing = false; } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../ERC1967/ERC1967UpgradeUpgradeable.sol"; import "./Initializable.sol"; /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. * * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing * `UUPSUpgradeable` with a custom implementation of upgrades. * * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. * * _Available since v4.1._ */ abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable { function __UUPSUpgradeable_init() internal initializer { __ERC1967Upgrade_init_unchained(); __UUPSUpgradeable_init_unchained(); } function __UUPSUpgradeable_init_unchained() internal initializer { } /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment address private immutable __self = address(this); /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to * fail. */ modifier onlyProxy() { require(address(this) != __self, "Function must be called through delegatecall"); require(_getImplementation() == __self, "Function must be called through active proxy"); _; } /** * @dev Upgrade the implementation of the proxy to `newImplementation`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. */ function upgradeTo(address newImplementation) external virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, new bytes(0), false); } /** * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call * encoded in `data`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. */ function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallSecure(newImplementation, data, true); } /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by * {upgradeTo} and {upgradeToAndCall}. * * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity * function _authorizeUpgrade(address) internal override onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../utils/ContextUpgradeable.sol"; import "../proxy/utils/Initializable.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ function __Ownable_init() internal initializer { __Context_init_unchained(); __Ownable_init_unchained(); } function __Ownable_init_unchained() internal initializer { _setOwner(_msgSender()); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _setOwner(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _setOwner(newOwner); } function _setOwner(address newOwner) private { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } uint256[49] private __gap; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol) pragma solidity 0.8.4; import { ErrorHandler } from "./ErrorHandler.sol"; /** * @notice Copied from OpenZeppelin's SafeCast.sol, adapted to use just in the required * uint sizes. * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such 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. * * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing * all math on `uint256` and `int256` and then downcasting. */ library SafeCast { using ErrorHandler for bytes4; /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits */ function toUint248(uint256 _value) internal pure returns (uint248) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint248(uint256))"))` bytes4 fnSelector = 0x3fd72672; fnSelector.verifyInput(_value <= type(uint248).max, 0); return uint248(_value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 _value) internal pure returns (uint128) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint128(uint256))"))` bytes4 fnSelector = 0x809fdd33; fnSelector.verifyInput(_value <= type(uint128).max, 0); return uint128(_value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits */ function toUint120(uint256 _value) internal pure returns (uint120) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint120(uint256))"))` bytes4 fnSelector = 0x1e4e4bad; fnSelector.verifyInput(_value <= type(uint120).max, 0); return uint120(_value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 _value) internal pure returns (uint64) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint64(uint256))"))` bytes4 fnSelector = 0x2665fad0; fnSelector.verifyInput(_value <= type(uint64).max, 0); return uint64(_value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 _value) internal pure returns (uint32) { // we're using selector to simplify input and state validation // internal function simulated selector is `bytes4(keccak256("toUint32(uint256))"))` bytes4 fnSelector = 0xc8193255; fnSelector.verifyInput(_value <= type(uint32).max, 0); return uint32(_value); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; /// @title Function for getting block timestamp. /// @dev Base contract that is overridden for tests. abstract contract Timestamp { /** * @dev Testing time-dependent functionality is difficult and the best way of * doing it is to override time in helper test smart contracts. * * @return `block.timestamp` in mainnet, custom values in testnets (if overridden). */ function _now256() internal view virtual returns (uint256) { // return current block timestamp return block.timestamp; } /** * @dev Empty reserved space in storage. The size of the __gap array is calculated so that * the amount of storage used by a contract always adds up to the 50. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { Stake } from "../libraries/Stake.sol"; interface ICorePool { function users(address _user) external view returns ( uint128, uint128, uint128, uint248, uint8, uint256, uint256 ); function poolToken() external view returns (address); function isFlashPool() external view returns (bool); function weight() external view returns (uint32); function lastYieldDistribution() external view returns (uint64); function yieldRewardsPerWeight() external view returns (uint256); function globalWeight() external view returns (uint256); function pendingRewards(address _user) external view returns (uint256, uint256); function poolTokenReserve() external view returns (uint256); function balanceOf(address _user) external view returns (uint256); function getTotalReserves() external view returns (uint256); function getStake(address _user, uint256 _stakeId) external view returns (Stake.Data memory); function getStakesLength(address _user) external view returns (uint256); function sync() external; function setWeight(uint32 _weight) external; function receiveVaultRewards(uint256 value) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; interface IERC20Mintable is IERC20Upgradeable { function mint(address _to, uint256 _value) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; /** * @title Errors Library. * * @notice Introduces some very common input and state validation for smart contracts, * such as non-zero input validation, general boolean expression validation, access validation. * * @notice Throws pre-defined errors instead of string error messages to reduce gas costs. * * @notice Since the library handles only very common errors, concrete smart contracts may * also introduce their own error types and handling. * * @author Basil Gorin */ library ErrorHandler { /** * @notice Thrown on zero input at index specified in a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param paramIndex function parameter index which caused an error thrown */ error ZeroInput(bytes4 fnSelector, uint8 paramIndex); /** * @notice Thrown on invalid input at index specified in a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param paramIndex function parameter index which caused an error thrown */ error InvalidInput(bytes4 fnSelector, uint8 paramIndex); /** * @notice Thrown on invalid state in a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param errorCode unique error code determining the exact place in code where error was thrown */ error InvalidState(bytes4 fnSelector, uint256 errorCode); /** * @notice Thrown on invalid access to a function specified. * * @param fnSelector function selector, defines a function where error was thrown * @param addr an address which access was denied, usually transaction sender */ error AccessDenied(bytes4 fnSelector, address addr); /** * @notice Verifies an input is set (non-zero). * * @param fnSelector function selector, defines a function which called the verification * @param value a value to check if it's set (non-zero) * @param paramIndex function parameter index which is verified */ function verifyNonZeroInput( bytes4 fnSelector, uint256 value, uint8 paramIndex ) internal pure { if (value == 0) { revert ZeroInput(fnSelector, paramIndex); } } /** * @notice Verifies an input is correct. * * @param fnSelector function selector, defines a function which called the verification * @param expr a boolean expression used to verify the input * @param paramIndex function parameter index which is verified */ function verifyInput( bytes4 fnSelector, bool expr, uint8 paramIndex ) internal pure { if (!expr) { revert InvalidInput(fnSelector, paramIndex); } } /** * @notice Verifies smart contract state is correct. * * @param fnSelector function selector, defines a function which called the verification * @param expr a boolean expression used to verify the contract state * @param errorCode unique error code determining the exact place in code which is verified */ function verifyState( bytes4 fnSelector, bool expr, uint256 errorCode ) internal pure { if (!expr) { revert InvalidState(fnSelector, errorCode); } } /** * @notice Verifies an access to the function. * * @param fnSelector function selector, defines a function which called the verification * @param expr a boolean expression used to verify the access */ function verifyAccess(bytes4 fnSelector, bool expr) internal view { if (!expr) { revert AccessDenied(fnSelector, msg.sender); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.2; import "../beacon/IBeaconUpgradeable.sol"; import "../../utils/AddressUpgradeable.sol"; import "../../utils/StorageSlotUpgradeable.sol"; import "../utils/Initializable.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. * * _Available since v4.1._ * * @custom:oz-upgrades-unsafe-allow delegatecall */ abstract contract ERC1967UpgradeUpgradeable is Initializable { function __ERC1967Upgrade_init() internal initializer { __ERC1967Upgrade_init_unchained(); } function __ERC1967Upgrade_init_unchained() internal initializer { } // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Returns the current implementation address. */ function _getImplementation() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * * Emits an {Upgraded} event. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Perform implementation upgrade with additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCall( address newImplementation, bytes memory data, bool forceCall ) internal { _upgradeTo(newImplementation); if (data.length > 0 || forceCall) { _functionDelegateCall(newImplementation, data); } } /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCallSecure( address newImplementation, bytes memory data, bool forceCall ) internal { address oldImplementation = _getImplementation(); // Initial upgrade and setup call _setImplementation(newImplementation); if (data.length > 0 || forceCall) { _functionDelegateCall(newImplementation, data); } // Perform rollback test if not already in progress StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT); if (!rollbackTesting.value) { // Trigger rollback using upgradeTo from the new implementation rollbackTesting.value = true; _functionDelegateCall( newImplementation, abi.encodeWithSignature("upgradeTo(address)", oldImplementation) ); rollbackTesting.value = false; // Check rollback was effective require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); // Finally reset to the new implementation and log the upgrade _upgradeTo(newImplementation); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { require(newAdmin != address(0), "ERC1967: new admin is the zero address"); StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. */ function _changeAdmin(address newAdmin) internal { emit AdminChanged(_getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Emitted when the beacon is upgraded. */ event BeaconUpgraded(address indexed beacon); /** * @dev Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract"); require( AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon; } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * * Emits a {BeaconUpgraded} event. */ function _upgradeBeaconToAndCall( address newBeacon, bytes memory data, bool forceCall ) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { _functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data); } } /** * @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) private returns (bytes memory) { require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed"); } uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeaconUpgradeable { /** * @dev Must return an address that can be used as a delegate call target. * * {BeaconProxy} will check that this address is a contract. */ function implementation() external view returns (address); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Collection of functions related to the address type */ library AddressUpgradeable { /** * @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; assembly { size := extcodesize(account) } return size > 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://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, "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 functionCall(target, data, "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"); require(isContract(target), "Address: call to non-contract"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResult(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) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason 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 { // 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 assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlotUpgradeable { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { assembly { r.slot := slot } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../proxy/utils/Initializable.sol"; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract ContextUpgradeable is Initializable { function __Context_init() internal initializer { __Context_init_unchained(); } function __Context_init_unchained() internal initializer { } function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } uint256[50] private __gap; } // SPDX-License-Identifier: MIT pragma solidity 0.8.4; /** * @dev Stake library used by ILV pool and Sushi LP Pool. * * @dev Responsible to manage weight calculation and store important constants * related to stake period, base weight and multipliers utilized. */ library Stake { struct Data { /// @dev token amount staked uint120 value; /// @dev locking period - from uint64 lockedFrom; /// @dev locking period - until uint64 lockedUntil; /// @dev indicates if the stake was created as a yield reward bool isYield; } /** * @dev Stake weight is proportional to stake value and time locked, precisely * "stake value wei multiplied by (fraction of the year locked plus one)". * @dev To avoid significant precision loss due to multiplication by "fraction of the year" [0, 1], * weight is stored multiplied by 1e6 constant, as an integer. * @dev Corner case 1: if time locked is zero, weight is stake value multiplied by 1e6 + base weight * @dev Corner case 2: if time locked is two years, division of (lockedUntil - lockedFrom) / MAX_STAKE_PERIOD is 1e6, and * weight is a stake value multiplied by 2 * 1e6. */ uint256 internal constant WEIGHT_MULTIPLIER = 1e6; /** * @dev Minimum weight value, if result of multiplication using WEIGHT_MULTIPLIER * is 0 (e.g stake flexible), then BASE_WEIGHT is used. */ uint256 internal constant BASE_WEIGHT = 1e6; /** * @dev Minimum period that someone can lock a stake for. */ uint256 internal constant MIN_STAKE_PERIOD = 30 days; /** * @dev Maximum period that someone can lock a stake for. */ uint256 internal constant MAX_STAKE_PERIOD = 365 days; /** * @dev Rewards per weight are stored multiplied by 1e20 as uint. */ uint256 internal constant REWARD_PER_WEIGHT_MULTIPLIER = 1e20; /** * @dev When we know beforehand that staking is done for yield instead of * executing `weight()` function we use the following constant. */ uint256 internal constant YIELD_STAKE_WEIGHT_MULTIPLIER = 2 * 1e6; function weight(Data storage _self) internal view returns (uint256) { return uint256( (((_self.lockedUntil - _self.lockedFrom) * WEIGHT_MULTIPLIER) / MAX_STAKE_PERIOD + BASE_WEIGHT) * _self.value ); } /** * @dev Converts stake weight (not to be mixed with the pool weight) to * ILV reward value, applying the 10^12 division on weight * * @param _weight stake weight * @param _rewardPerWeight ILV reward per weight * @param _rewardPerWeightPaid last reward per weight value used for user earnings * @return reward value normalized to 10^12 */ function earned( uint256 _weight, uint256 _rewardPerWeight, uint256 _rewardPerWeightPaid ) internal pure returns (uint256) { // apply the formula and return return (_weight * (_rewardPerWeight - _rewardPerWeightPaid)) / REWARD_PER_WEIGHT_MULTIPLIER; } /** * @dev Converts reward ILV value to stake weight (not to be mixed with the pool weight), * applying the 10^12 multiplication on the reward. * - OR - * @dev Converts reward ILV value to reward/weight if stake weight is supplied as second * function parameter instead of reward/weight. * * @param _reward yield reward * @param _globalWeight total weight in the pool * @return reward per weight value */ function getRewardPerWeight(uint256 _reward, uint256 _globalWeight) internal pure returns (uint256) { // apply the reverse formula and return return (_reward * REWARD_PER_WEIGHT_MULTIPLIER) / _globalWeight; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20Upgradeable { /** * @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); }