Created
May 24, 2022 10:46
-
-
Save arwer13/13db539c22f2447d840a41569b42f729 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Sources flattened with hardhat v2.8.4 https://hardhat.org | |
// File @aragon/os/contracts/common/UnstructuredStorage.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
library UnstructuredStorage { | |
function getStorageBool(bytes32 position) internal view returns (bool data) { | |
assembly { data := sload(position) } | |
} | |
function getStorageAddress(bytes32 position) internal view returns (address data) { | |
assembly { data := sload(position) } | |
} | |
function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { | |
assembly { data := sload(position) } | |
} | |
function getStorageUint256(bytes32 position) internal view returns (uint256 data) { | |
assembly { data := sload(position) } | |
} | |
function setStorageBool(bytes32 position, bool data) internal { | |
assembly { sstore(position, data) } | |
} | |
function setStorageAddress(bytes32 position, address data) internal { | |
assembly { sstore(position, data) } | |
} | |
function setStorageBytes32(bytes32 position, bytes32 data) internal { | |
assembly { sstore(position, data) } | |
} | |
function setStorageUint256(bytes32 position, uint256 data) internal { | |
assembly { sstore(position, data) } | |
} | |
} | |
// File @aragon/os/contracts/acl/IACL.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
interface IACL { | |
function initialize(address permissionsCreator) external; | |
// TODO: this should be external | |
// See https://github.com/ethereum/solidity/issues/4832 | |
function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); | |
} | |
// File @aragon/os/contracts/common/IVaultRecoverable.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
interface IVaultRecoverable { | |
event RecoverToVault(address indexed vault, address indexed token, uint256 amount); | |
function transferToVault(address token) external; | |
function allowRecoverability(address token) external view returns (bool); | |
function getRecoveryVault() external view returns (address); | |
} | |
// File @aragon/os/contracts/kernel/IKernel.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
interface IKernelEvents { | |
event SetApp(bytes32 indexed namespace, bytes32 indexed appId, address app); | |
} | |
// This should be an interface, but interfaces can't inherit yet :( | |
contract IKernel is IKernelEvents, IVaultRecoverable { | |
function acl() public view returns (IACL); | |
function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); | |
function setApp(bytes32 namespace, bytes32 appId, address app) public; | |
function getApp(bytes32 namespace, bytes32 appId) public view returns (address); | |
} | |
// File @aragon/os/contracts/apps/AppStorage.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract AppStorage { | |
using UnstructuredStorage for bytes32; | |
/* Hardcoded constants to save gas | |
bytes32 internal constant KERNEL_POSITION = keccak256("aragonOS.appStorage.kernel"); | |
bytes32 internal constant APP_ID_POSITION = keccak256("aragonOS.appStorage.appId"); | |
*/ | |
bytes32 internal constant KERNEL_POSITION = 0x4172f0f7d2289153072b0a6ca36959e0cbe2efc3afe50fc81636caa96338137b; | |
bytes32 internal constant APP_ID_POSITION = 0xd625496217aa6a3453eecb9c3489dc5a53e6c67b444329ea2b2cbc9ff547639b; | |
function kernel() public view returns (IKernel) { | |
return IKernel(KERNEL_POSITION.getStorageAddress()); | |
} | |
function appId() public view returns (bytes32) { | |
return APP_ID_POSITION.getStorageBytes32(); | |
} | |
function setKernel(IKernel _kernel) internal { | |
KERNEL_POSITION.setStorageAddress(address(_kernel)); | |
} | |
function setAppId(bytes32 _appId) internal { | |
APP_ID_POSITION.setStorageBytes32(_appId); | |
} | |
} | |
// File @aragon/os/contracts/acl/ACLSyntaxSugar.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract ACLSyntaxSugar { | |
function arr() internal pure returns (uint256[]) { | |
return new uint256[](0); | |
} | |
function arr(bytes32 _a) internal pure returns (uint256[] r) { | |
return arr(uint256(_a)); | |
} | |
function arr(bytes32 _a, bytes32 _b) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), uint256(_b)); | |
} | |
function arr(address _a) internal pure returns (uint256[] r) { | |
return arr(uint256(_a)); | |
} | |
function arr(address _a, address _b) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), uint256(_b)); | |
} | |
function arr(address _a, uint256 _b, uint256 _c) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), _b, _c); | |
} | |
function arr(address _a, uint256 _b, uint256 _c, uint256 _d) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), _b, _c, _d); | |
} | |
function arr(address _a, uint256 _b) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), uint256(_b)); | |
} | |
function arr(address _a, address _b, uint256 _c, uint256 _d, uint256 _e) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), uint256(_b), _c, _d, _e); | |
} | |
function arr(address _a, address _b, address _c) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), uint256(_b), uint256(_c)); | |
} | |
function arr(address _a, address _b, uint256 _c) internal pure returns (uint256[] r) { | |
return arr(uint256(_a), uint256(_b), uint256(_c)); | |
} | |
function arr(uint256 _a) internal pure returns (uint256[] r) { | |
r = new uint256[](1); | |
r[0] = _a; | |
} | |
function arr(uint256 _a, uint256 _b) internal pure returns (uint256[] r) { | |
r = new uint256[](2); | |
r[0] = _a; | |
r[1] = _b; | |
} | |
function arr(uint256 _a, uint256 _b, uint256 _c) internal pure returns (uint256[] r) { | |
r = new uint256[](3); | |
r[0] = _a; | |
r[1] = _b; | |
r[2] = _c; | |
} | |
function arr(uint256 _a, uint256 _b, uint256 _c, uint256 _d) internal pure returns (uint256[] r) { | |
r = new uint256[](4); | |
r[0] = _a; | |
r[1] = _b; | |
r[2] = _c; | |
r[3] = _d; | |
} | |
function arr(uint256 _a, uint256 _b, uint256 _c, uint256 _d, uint256 _e) internal pure returns (uint256[] r) { | |
r = new uint256[](5); | |
r[0] = _a; | |
r[1] = _b; | |
r[2] = _c; | |
r[3] = _d; | |
r[4] = _e; | |
} | |
} | |
contract ACLHelpers { | |
function decodeParamOp(uint256 _x) internal pure returns (uint8 b) { | |
return uint8(_x >> (8 * 30)); | |
} | |
function decodeParamId(uint256 _x) internal pure returns (uint8 b) { | |
return uint8(_x >> (8 * 31)); | |
} | |
function decodeParamsList(uint256 _x) internal pure returns (uint32 a, uint32 b, uint32 c) { | |
a = uint32(_x); | |
b = uint32(_x >> (8 * 4)); | |
c = uint32(_x >> (8 * 8)); | |
} | |
} | |
// File @aragon/os/contracts/common/Uint256Helpers.sol@v4.4.0 | |
pragma solidity ^0.4.24; | |
library Uint256Helpers { | |
uint256 private constant MAX_UINT64 = uint64(-1); | |
string private constant ERROR_NUMBER_TOO_BIG = "UINT64_NUMBER_TOO_BIG"; | |
function toUint64(uint256 a) internal pure returns (uint64) { | |
require(a <= MAX_UINT64, ERROR_NUMBER_TOO_BIG); | |
return uint64(a); | |
} | |
} | |
// File @aragon/os/contracts/common/TimeHelpers.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract TimeHelpers { | |
using Uint256Helpers for uint256; | |
/** | |
* @dev Returns the current block number. | |
* Using a function rather than `block.number` allows us to easily mock the block number in | |
* tests. | |
*/ | |
function getBlockNumber() internal view returns (uint256) { | |
return block.number; | |
} | |
/** | |
* @dev Returns the current block number, converted to uint64. | |
* Using a function rather than `block.number` allows us to easily mock the block number in | |
* tests. | |
*/ | |
function getBlockNumber64() internal view returns (uint64) { | |
return getBlockNumber().toUint64(); | |
} | |
/** | |
* @dev Returns the current timestamp. | |
* Using a function rather than `block.timestamp` allows us to easily mock it in | |
* tests. | |
*/ | |
function getTimestamp() internal view returns (uint256) { | |
return block.timestamp; // solium-disable-line security/no-block-members | |
} | |
/** | |
* @dev Returns the current timestamp, converted to uint64. | |
* Using a function rather than `block.timestamp` allows us to easily mock it in | |
* tests. | |
*/ | |
function getTimestamp64() internal view returns (uint64) { | |
return getTimestamp().toUint64(); | |
} | |
} | |
// File @aragon/os/contracts/common/Initializable.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract Initializable is TimeHelpers { | |
using UnstructuredStorage for bytes32; | |
// keccak256("aragonOS.initializable.initializationBlock") | |
bytes32 internal constant INITIALIZATION_BLOCK_POSITION = 0xebb05b386a8d34882b8711d156f463690983dc47815980fb82aeeff1aa43579e; | |
string private constant ERROR_ALREADY_INITIALIZED = "INIT_ALREADY_INITIALIZED"; | |
string private constant ERROR_NOT_INITIALIZED = "INIT_NOT_INITIALIZED"; | |
modifier onlyInit { | |
require(getInitializationBlock() == 0, ERROR_ALREADY_INITIALIZED); | |
_; | |
} | |
modifier isInitialized { | |
require(hasInitialized(), ERROR_NOT_INITIALIZED); | |
_; | |
} | |
/** | |
* @return Block number in which the contract was initialized | |
*/ | |
function getInitializationBlock() public view returns (uint256) { | |
return INITIALIZATION_BLOCK_POSITION.getStorageUint256(); | |
} | |
/** | |
* @return Whether the contract has been initialized by the time of the current block | |
*/ | |
function hasInitialized() public view returns (bool) { | |
uint256 initializationBlock = getInitializationBlock(); | |
return initializationBlock != 0 && getBlockNumber() >= initializationBlock; | |
} | |
/** | |
* @dev Function to be called by top level contract after initialization has finished. | |
*/ | |
function initialized() internal onlyInit { | |
INITIALIZATION_BLOCK_POSITION.setStorageUint256(getBlockNumber()); | |
} | |
/** | |
* @dev Function to be called by top level contract after initialization to enable the contract | |
* at a future block number rather than immediately. | |
*/ | |
function initializedAt(uint256 _blockNumber) internal onlyInit { | |
INITIALIZATION_BLOCK_POSITION.setStorageUint256(_blockNumber); | |
} | |
} | |
// File @aragon/os/contracts/common/Petrifiable.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract Petrifiable is Initializable { | |
// Use block UINT256_MAX (which should be never) as the initializable date | |
uint256 internal constant PETRIFIED_BLOCK = uint256(-1); | |
function isPetrified() public view returns (bool) { | |
return getInitializationBlock() == PETRIFIED_BLOCK; | |
} | |
/** | |
* @dev Function to be called by top level contract to prevent being initialized. | |
* Useful for freezing base contracts when they're used behind proxies. | |
*/ | |
function petrify() internal onlyInit { | |
initializedAt(PETRIFIED_BLOCK); | |
} | |
} | |
// File @aragon/os/contracts/common/Autopetrified.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract Autopetrified is Petrifiable { | |
constructor() public { | |
// Immediately petrify base (non-proxy) instances of inherited contracts on deploy. | |
// This renders them uninitializable (and unusable without a proxy). | |
petrify(); | |
} | |
} | |
// File @aragon/os/contracts/common/ConversionHelpers.sol@v4.4.0 | |
pragma solidity ^0.4.24; | |
library ConversionHelpers { | |
string private constant ERROR_IMPROPER_LENGTH = "CONVERSION_IMPROPER_LENGTH"; | |
function dangerouslyCastUintArrayToBytes(uint256[] memory _input) internal pure returns (bytes memory output) { | |
// Force cast the uint256[] into a bytes array, by overwriting its length | |
// Note that the bytes array doesn't need to be initialized as we immediately overwrite it | |
// with the input and a new length. The input becomes invalid from this point forward. | |
uint256 byteLength = _input.length * 32; | |
assembly { | |
output := _input | |
mstore(output, byteLength) | |
} | |
} | |
function dangerouslyCastBytesToUintArray(bytes memory _input) internal pure returns (uint256[] memory output) { | |
// Force cast the bytes array into a uint256[], by overwriting its length | |
// Note that the uint256[] doesn't need to be initialized as we immediately overwrite it | |
// with the input and a new length. The input becomes invalid from this point forward. | |
uint256 intsLength = _input.length / 32; | |
require(_input.length == intsLength * 32, ERROR_IMPROPER_LENGTH); | |
assembly { | |
output := _input | |
mstore(output, intsLength) | |
} | |
} | |
} | |
// File @aragon/os/contracts/common/ReentrancyGuard.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract ReentrancyGuard { | |
using UnstructuredStorage for bytes32; | |
/* Hardcoded constants to save gas | |
bytes32 internal constant REENTRANCY_MUTEX_POSITION = keccak256("aragonOS.reentrancyGuard.mutex"); | |
*/ | |
bytes32 private constant REENTRANCY_MUTEX_POSITION = 0xe855346402235fdd185c890e68d2c4ecad599b88587635ee285bce2fda58dacb; | |
string private constant ERROR_REENTRANT = "REENTRANCY_REENTRANT_CALL"; | |
modifier nonReentrant() { | |
// Ensure mutex is unlocked | |
require(!REENTRANCY_MUTEX_POSITION.getStorageBool(), ERROR_REENTRANT); | |
// Lock mutex before function call | |
REENTRANCY_MUTEX_POSITION.setStorageBool(true); | |
// Perform function call | |
_; | |
// Unlock mutex after function call | |
REENTRANCY_MUTEX_POSITION.setStorageBool(false); | |
} | |
} | |
// File @aragon/os/contracts/lib/token/ERC20.sol@v4.4.0 | |
// See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/a9f910d34f0ab33a1ae5e714f69f9596a02b4d91/contracts/token/ERC20/ERC20.sol | |
pragma solidity ^0.4.24; | |
/** | |
* @title ERC20 interface | |
* @dev see https://github.com/ethereum/EIPs/issues/20 | |
*/ | |
contract ERC20 { | |
function totalSupply() public view returns (uint256); | |
function balanceOf(address _who) public view returns (uint256); | |
function allowance(address _owner, address _spender) | |
public view returns (uint256); | |
function transfer(address _to, uint256 _value) public returns (bool); | |
function approve(address _spender, uint256 _value) | |
public returns (bool); | |
function transferFrom(address _from, address _to, uint256 _value) | |
public returns (bool); | |
event Transfer( | |
address indexed from, | |
address indexed to, | |
uint256 value | |
); | |
event Approval( | |
address indexed owner, | |
address indexed spender, | |
uint256 value | |
); | |
} | |
// File @aragon/os/contracts/common/EtherTokenConstant.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
// aragonOS and aragon-apps rely on address(0) to denote native ETH, in | |
// contracts where both tokens and ETH are accepted | |
contract EtherTokenConstant { | |
address internal constant ETH = address(0); | |
} | |
// File @aragon/os/contracts/common/IsContract.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract IsContract { | |
/* | |
* NOTE: this should NEVER be used for authentication | |
* (see pitfalls: https://github.com/fergarrui/ethereum-security/tree/master/contracts/extcodesize). | |
* | |
* This is only intended to be used as a sanity check that an address is actually a contract, | |
* RATHER THAN an address not being a contract. | |
*/ | |
function isContract(address _target) internal view returns (bool) { | |
if (_target == address(0)) { | |
return false; | |
} | |
uint256 size; | |
assembly { size := extcodesize(_target) } | |
return size > 0; | |
} | |
} | |
// File @aragon/os/contracts/common/SafeERC20.sol@v4.4.0 | |
// Inspired by AdEx (https://github.com/AdExNetwork/adex-protocol-eth/blob/b9df617829661a7518ee10f4cb6c4108659dd6d5/contracts/libs/SafeERC20.sol) | |
// and 0x (https://github.com/0xProject/0x-monorepo/blob/737d1dc54d72872e24abce5a1dbe1b66d35fa21a/contracts/protocol/contracts/protocol/AssetProxy/ERC20Proxy.sol#L143) | |
pragma solidity ^0.4.24; | |
library SafeERC20 { | |
// Before 0.5, solidity has a mismatch between `address.transfer()` and `token.transfer()`: | |
// https://github.com/ethereum/solidity/issues/3544 | |
bytes4 private constant TRANSFER_SELECTOR = 0xa9059cbb; | |
string private constant ERROR_TOKEN_BALANCE_REVERTED = "SAFE_ERC_20_BALANCE_REVERTED"; | |
string private constant ERROR_TOKEN_ALLOWANCE_REVERTED = "SAFE_ERC_20_ALLOWANCE_REVERTED"; | |
function invokeAndCheckSuccess(address _addr, bytes memory _calldata) | |
private | |
returns (bool) | |
{ | |
bool ret; | |
assembly { | |
let ptr := mload(0x40) // free memory pointer | |
let success := call( | |
gas, // forward all gas | |
_addr, // address | |
0, // no value | |
add(_calldata, 0x20), // calldata start | |
mload(_calldata), // calldata length | |
ptr, // write output over free memory | |
0x20 // uint256 return | |
) | |
if gt(success, 0) { | |
// Check number of bytes returned from last function call | |
switch returndatasize | |
// No bytes returned: assume success | |
case 0 { | |
ret := 1 | |
} | |
// 32 bytes returned: check if non-zero | |
case 0x20 { | |
// Only return success if returned data was true | |
// Already have output in ptr | |
ret := eq(mload(ptr), 1) | |
} | |
// Not sure what was returned: don't mark as success | |
default { } | |
} | |
} | |
return ret; | |
} | |
function staticInvoke(address _addr, bytes memory _calldata) | |
private | |
view | |
returns (bool, uint256) | |
{ | |
bool success; | |
uint256 ret; | |
assembly { | |
let ptr := mload(0x40) // free memory pointer | |
success := staticcall( | |
gas, // forward all gas | |
_addr, // address | |
add(_calldata, 0x20), // calldata start | |
mload(_calldata), // calldata length | |
ptr, // write output over free memory | |
0x20 // uint256 return | |
) | |
if gt(success, 0) { | |
ret := mload(ptr) | |
} | |
} | |
return (success, ret); | |
} | |
/** | |
* @dev Same as a standards-compliant ERC20.transfer() that never reverts (returns false). | |
* Note that this makes an external call to the token. | |
*/ | |
function safeTransfer(ERC20 _token, address _to, uint256 _amount) internal returns (bool) { | |
bytes memory transferCallData = abi.encodeWithSelector( | |
TRANSFER_SELECTOR, | |
_to, | |
_amount | |
); | |
return invokeAndCheckSuccess(_token, transferCallData); | |
} | |
/** | |
* @dev Same as a standards-compliant ERC20.transferFrom() that never reverts (returns false). | |
* Note that this makes an external call to the token. | |
*/ | |
function safeTransferFrom(ERC20 _token, address _from, address _to, uint256 _amount) internal returns (bool) { | |
bytes memory transferFromCallData = abi.encodeWithSelector( | |
_token.transferFrom.selector, | |
_from, | |
_to, | |
_amount | |
); | |
return invokeAndCheckSuccess(_token, transferFromCallData); | |
} | |
/** | |
* @dev Same as a standards-compliant ERC20.approve() that never reverts (returns false). | |
* Note that this makes an external call to the token. | |
*/ | |
function safeApprove(ERC20 _token, address _spender, uint256 _amount) internal returns (bool) { | |
bytes memory approveCallData = abi.encodeWithSelector( | |
_token.approve.selector, | |
_spender, | |
_amount | |
); | |
return invokeAndCheckSuccess(_token, approveCallData); | |
} | |
/** | |
* @dev Static call into ERC20.balanceOf(). | |
* Reverts if the call fails for some reason (should never fail). | |
*/ | |
function staticBalanceOf(ERC20 _token, address _owner) internal view returns (uint256) { | |
bytes memory balanceOfCallData = abi.encodeWithSelector( | |
_token.balanceOf.selector, | |
_owner | |
); | |
(bool success, uint256 tokenBalance) = staticInvoke(_token, balanceOfCallData); | |
require(success, ERROR_TOKEN_BALANCE_REVERTED); | |
return tokenBalance; | |
} | |
/** | |
* @dev Static call into ERC20.allowance(). | |
* Reverts if the call fails for some reason (should never fail). | |
*/ | |
function staticAllowance(ERC20 _token, address _owner, address _spender) internal view returns (uint256) { | |
bytes memory allowanceCallData = abi.encodeWithSelector( | |
_token.allowance.selector, | |
_owner, | |
_spender | |
); | |
(bool success, uint256 allowance) = staticInvoke(_token, allowanceCallData); | |
require(success, ERROR_TOKEN_ALLOWANCE_REVERTED); | |
return allowance; | |
} | |
/** | |
* @dev Static call into ERC20.totalSupply(). | |
* Reverts if the call fails for some reason (should never fail). | |
*/ | |
function staticTotalSupply(ERC20 _token) internal view returns (uint256) { | |
bytes memory totalSupplyCallData = abi.encodeWithSelector(_token.totalSupply.selector); | |
(bool success, uint256 totalSupply) = staticInvoke(_token, totalSupplyCallData); | |
require(success, ERROR_TOKEN_ALLOWANCE_REVERTED); | |
return totalSupply; | |
} | |
} | |
// File @aragon/os/contracts/common/VaultRecoverable.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract VaultRecoverable is IVaultRecoverable, EtherTokenConstant, IsContract { | |
using SafeERC20 for ERC20; | |
string private constant ERROR_DISALLOWED = "RECOVER_DISALLOWED"; | |
string private constant ERROR_VAULT_NOT_CONTRACT = "RECOVER_VAULT_NOT_CONTRACT"; | |
string private constant ERROR_TOKEN_TRANSFER_FAILED = "RECOVER_TOKEN_TRANSFER_FAILED"; | |
/** | |
* @notice Send funds to recovery Vault. This contract should never receive funds, | |
* but in case it does, this function allows one to recover them. | |
* @param _token Token balance to be sent to recovery vault. | |
*/ | |
function transferToVault(address _token) external { | |
require(allowRecoverability(_token), ERROR_DISALLOWED); | |
address vault = getRecoveryVault(); | |
require(isContract(vault), ERROR_VAULT_NOT_CONTRACT); | |
uint256 balance; | |
if (_token == ETH) { | |
balance = address(this).balance; | |
vault.transfer(balance); | |
} else { | |
ERC20 token = ERC20(_token); | |
balance = token.staticBalanceOf(this); | |
require(token.safeTransfer(vault, balance), ERROR_TOKEN_TRANSFER_FAILED); | |
} | |
emit RecoverToVault(vault, _token, balance); | |
} | |
/** | |
* @dev By default deriving from AragonApp makes it recoverable | |
* @param token Token address that would be recovered | |
* @return bool whether the app allows the recovery | |
*/ | |
function allowRecoverability(address token) public view returns (bool) { | |
return true; | |
} | |
// Cast non-implemented interface to be public so we can use it internally | |
function getRecoveryVault() public view returns (address); | |
} | |
// File @aragon/os/contracts/evmscript/IEVMScriptExecutor.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
interface IEVMScriptExecutor { | |
function execScript(bytes script, bytes input, address[] blacklist) external returns (bytes); | |
function executorType() external pure returns (bytes32); | |
} | |
// File @aragon/os/contracts/evmscript/IEVMScriptRegistry.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract EVMScriptRegistryConstants { | |
/* Hardcoded constants to save gas | |
bytes32 internal constant EVMSCRIPT_REGISTRY_APP_ID = apmNamehash("evmreg"); | |
*/ | |
bytes32 internal constant EVMSCRIPT_REGISTRY_APP_ID = 0xddbcfd564f642ab5627cf68b9b7d374fb4f8a36e941a75d89c87998cef03bd61; | |
} | |
interface IEVMScriptRegistry { | |
function addScriptExecutor(IEVMScriptExecutor executor) external returns (uint id); | |
function disableScriptExecutor(uint256 executorId) external; | |
// TODO: this should be external | |
// See https://github.com/ethereum/solidity/issues/4832 | |
function getScriptExecutor(bytes script) public view returns (IEVMScriptExecutor); | |
} | |
// File @aragon/os/contracts/kernel/KernelConstants.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract KernelAppIds { | |
/* Hardcoded constants to save gas | |
bytes32 internal constant KERNEL_CORE_APP_ID = apmNamehash("kernel"); | |
bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = apmNamehash("acl"); | |
bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = apmNamehash("vault"); | |
*/ | |
bytes32 internal constant KERNEL_CORE_APP_ID = 0x3b4bf6bf3ad5000ecf0f989d5befde585c6860fea3e574a4fab4c49d1c177d9c; | |
bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = 0xe3262375f45a6e2026b7e7b18c2b807434f2508fe1a2a3dfb493c7df8f4aad6a; | |
bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = 0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1; | |
} | |
contract KernelNamespaceConstants { | |
/* Hardcoded constants to save gas | |
bytes32 internal constant KERNEL_CORE_NAMESPACE = keccak256("core"); | |
bytes32 internal constant KERNEL_APP_BASES_NAMESPACE = keccak256("base"); | |
bytes32 internal constant KERNEL_APP_ADDR_NAMESPACE = keccak256("app"); | |
*/ | |
bytes32 internal constant KERNEL_CORE_NAMESPACE = 0xc681a85306374a5ab27f0bbc385296a54bcd314a1948b6cf61c4ea1bc44bb9f8; | |
bytes32 internal constant KERNEL_APP_BASES_NAMESPACE = 0xf1f3eb40f5bc1ad1344716ced8b8a0431d840b5783aea1fd01786bc26f35ac0f; | |
bytes32 internal constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb; | |
} | |
// File @aragon/os/contracts/evmscript/EVMScriptRunner.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
contract EVMScriptRunner is AppStorage, Initializable, EVMScriptRegistryConstants, KernelNamespaceConstants { | |
string private constant ERROR_EXECUTOR_UNAVAILABLE = "EVMRUN_EXECUTOR_UNAVAILABLE"; | |
string private constant ERROR_PROTECTED_STATE_MODIFIED = "EVMRUN_PROTECTED_STATE_MODIFIED"; | |
/* This is manually crafted in assembly | |
string private constant ERROR_EXECUTOR_INVALID_RETURN = "EVMRUN_EXECUTOR_INVALID_RETURN"; | |
*/ | |
event ScriptResult(address indexed executor, bytes script, bytes input, bytes returnData); | |
function getEVMScriptExecutor(bytes _script) public view returns (IEVMScriptExecutor) { | |
return IEVMScriptExecutor(getEVMScriptRegistry().getScriptExecutor(_script)); | |
} | |
function getEVMScriptRegistry() public view returns (IEVMScriptRegistry) { | |
address registryAddr = kernel().getApp(KERNEL_APP_ADDR_NAMESPACE, EVMSCRIPT_REGISTRY_APP_ID); | |
return IEVMScriptRegistry(registryAddr); | |
} | |
function runScript(bytes _script, bytes _input, address[] _blacklist) | |
internal | |
isInitialized | |
protectState | |
returns (bytes) | |
{ | |
IEVMScriptExecutor executor = getEVMScriptExecutor(_script); | |
require(address(executor) != address(0), ERROR_EXECUTOR_UNAVAILABLE); | |
bytes4 sig = executor.execScript.selector; | |
bytes memory data = abi.encodeWithSelector(sig, _script, _input, _blacklist); | |
bytes memory output; | |
assembly { | |
let success := delegatecall( | |
gas, // forward all gas | |
executor, // address | |
add(data, 0x20), // calldata start | |
mload(data), // calldata length | |
0, // don't write output (we'll handle this ourselves) | |
0 // don't write output | |
) | |
output := mload(0x40) // free mem ptr get | |
switch success | |
case 0 { | |
// If the call errored, forward its full error data | |
returndatacopy(output, 0, returndatasize) | |
revert(output, returndatasize) | |
} | |
default { | |
switch gt(returndatasize, 0x3f) | |
case 0 { | |
// Need at least 0x40 bytes returned for properly ABI-encoded bytes values, | |
// revert with "EVMRUN_EXECUTOR_INVALID_RETURN" | |
// See remix: doing a `revert("EVMRUN_EXECUTOR_INVALID_RETURN")` always results in | |
// this memory layout | |
mstore(output, 0x08c379a000000000000000000000000000000000000000000000000000000000) // error identifier | |
mstore(add(output, 0x04), 0x0000000000000000000000000000000000000000000000000000000000000020) // starting offset | |
mstore(add(output, 0x24), 0x000000000000000000000000000000000000000000000000000000000000001e) // reason length | |
mstore(add(output, 0x44), 0x45564d52554e5f4558454355544f525f494e56414c49445f52455455524e0000) // reason | |
revert(output, 100) // 100 = 4 + 3 * 32 (error identifier + 3 words for the ABI encoded error) | |
} | |
default { | |
// Copy result | |
// | |
// Needs to perform an ABI decode for the expected `bytes` return type of | |
// `executor.execScript()` as solidity will automatically ABI encode the returned bytes as: | |
// [ position of the first dynamic length return value = 0x20 (32 bytes) ] | |
// [ output length (32 bytes) ] | |
// [ output content (N bytes) ] | |
// | |
// Perform the ABI decode by ignoring the first 32 bytes of the return data | |
let copysize := sub(returndatasize, 0x20) | |
returndatacopy(output, 0x20, copysize) | |
mstore(0x40, add(output, copysize)) // free mem ptr set | |
} | |
} | |
} | |
emit ScriptResult(address(executor), _script, _input, output); | |
return output; | |
} | |
modifier protectState { | |
address preKernel = address(kernel()); | |
bytes32 preAppId = appId(); | |
_; // exec | |
require(address(kernel()) == preKernel, ERROR_PROTECTED_STATE_MODIFIED); | |
require(appId() == preAppId, ERROR_PROTECTED_STATE_MODIFIED); | |
} | |
} | |
// File @aragon/os/contracts/apps/AragonApp.sol@v4.4.0 | |
/* | |
* SPDX-License-Identifier: MIT | |
*/ | |
pragma solidity ^0.4.24; | |
// Contracts inheriting from AragonApp are, by default, immediately petrified upon deployment so | |
// that they can never be initialized. | |
// Unless overriden, this behaviour enforces those contracts to be usable only behind an AppProxy. | |
// ReentrancyGuard, EVMScriptRunner, and ACLSyntaxSugar are not directly used by this contract, but | |
// are included so that they are automatically usable by subclassing contracts | |
contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGuard, EVMScriptRunner, ACLSyntaxSugar { | |
string private constant ERROR_AUTH_FAILED = "APP_AUTH_FAILED"; | |
modifier auth(bytes32 _role) { | |
require(canPerform(msg.sender, _role, new uint256[](0)), ERROR_AUTH_FAILED); | |
_; | |
} | |
modifier authP(bytes32 _role, uint256[] _params) { | |
require(canPerform(msg.sender, _role, _params), ERROR_AUTH_FAILED); | |
_; | |
} | |
/** | |
* @dev Check whether an action can be performed by a sender for a particular role on this app | |
* @param _sender Sender of the call | |
* @param _role Role on this app | |
* @param _params Permission params for the role | |
* @return Boolean indicating whether the sender has the permissions to perform the action. | |
* Always returns false if the app hasn't been initialized yet. | |
*/ | |
function canPerform(address _sender, bytes32 _role, uint256[] _params) public view returns (bool) { | |
if (!hasInitialized()) { | |
return false; | |
} | |
IKernel linkedKernel = kernel(); | |
if (address(linkedKernel) == address(0)) { | |
return false; | |
} | |
return linkedKernel.hasPermission( | |
_sender, | |
address(this), | |
_role, | |
ConversionHelpers.dangerouslyCastUintArrayToBytes(_params) | |
); | |
} | |
/** | |
* @dev Get the recovery vault for the app | |
* @return Recovery vault address for the app | |
*/ | |
function getRecoveryVault() public view returns (address) { | |
// Funds recovery via a vault is only available when used with a kernel | |
return kernel().getRecoveryVault(); // if kernel is not set, it will revert | |
} | |
} | |
// File @aragon/os/contracts/lib/math/SafeMath.sol@v4.4.0 | |
// See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/d51e38758e1d985661534534d5c61e27bece5042/contracts/math/SafeMath.sol | |
// Adapted to use pragma ^0.4.24 and satisfy our linter rules | |
pragma solidity ^0.4.24; | |
/** | |
* @title SafeMath | |
* @dev Math operations with safety checks that revert on error | |
*/ | |
library SafeMath { | |
string private constant ERROR_ADD_OVERFLOW = "MATH_ADD_OVERFLOW"; | |
string private constant ERROR_SUB_UNDERFLOW = "MATH_SUB_UNDERFLOW"; | |
string private constant ERROR_MUL_OVERFLOW = "MATH_MUL_OVERFLOW"; | |
string private constant ERROR_DIV_ZERO = "MATH_DIV_ZERO"; | |
/** | |
* @dev Multiplies two numbers, reverts on overflow. | |
*/ | |
function mul(uint256 _a, uint256 _b) internal pure returns (uint256) { | |
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the | |
// benefit is lost if 'b' is also tested. | |
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 | |
if (_a == 0) { | |
return 0; | |
} | |
uint256 c = _a * _b; | |
require(c / _a == _b, ERROR_MUL_OVERFLOW); | |
return c; | |
} | |
/** | |
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero. | |
*/ | |
function div(uint256 _a, uint256 _b) internal pure returns (uint256) { | |
require(_b > 0, ERROR_DIV_ZERO); // Solidity only automatically asserts when dividing by 0 | |
uint256 c = _a / _b; | |
// assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold | |
return c; | |
} | |
/** | |
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). | |
*/ | |
function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { | |
require(_b <= _a, ERROR_SUB_UNDERFLOW); | |
uint256 c = _a - _b; | |
return c; | |
} | |
/** | |
* @dev Adds two numbers, reverts on overflow. | |
*/ | |
function add(uint256 _a, uint256 _b) internal pure returns (uint256) { | |
uint256 c = _a + _b; | |
require(c >= _a, ERROR_ADD_OVERFLOW); | |
return c; | |
} | |
/** | |
* @dev Divides two numbers and returns the remainder (unsigned integer modulo), | |
* reverts when dividing by zero. | |
*/ | |
function mod(uint256 a, uint256 b) internal pure returns (uint256) { | |
require(b != 0, ERROR_DIV_ZERO); | |
return a % b; | |
} | |
} | |
// File openzeppelin-solidity/contracts/introspection/ERC165Checker.sol@v2.0.0 | |
pragma solidity ^0.4.24; | |
/** | |
* @title ERC165Checker | |
* @dev Use `using ERC165Checker for address`; to include this library | |
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md | |
*/ | |
library ERC165Checker { | |
// As per the EIP-165 spec, no interface should ever match 0xffffffff | |
bytes4 private constant _InterfaceId_Invalid = 0xffffffff; | |
bytes4 private constant _InterfaceId_ERC165 = 0x01ffc9a7; | |
/** | |
* 0x01ffc9a7 === | |
* bytes4(keccak256('supportsInterface(bytes4)')) | |
*/ | |
/** | |
* @notice Query if a contract supports ERC165 | |
* @param account The address of the contract to query for support of ERC165 | |
* @return true if the contract at account implements ERC165 | |
*/ | |
function _supportsERC165(address account) | |
internal | |
view | |
returns (bool) | |
{ | |
// Any contract that implements ERC165 must explicitly indicate support of | |
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid | |
return _supportsERC165Interface(account, _InterfaceId_ERC165) && | |
!_supportsERC165Interface(account, _InterfaceId_Invalid); | |
} | |
/** | |
* @notice Query if a contract implements an interface, also checks support of ERC165 | |
* @param account The address of the contract to query for support of an interface | |
* @param interfaceId The interface identifier, as specified in ERC-165 | |
* @return true if the contract at account indicates support of the interface with | |
* identifier interfaceId, false otherwise | |
* @dev Interface identification is specified in ERC-165. | |
*/ | |
function _supportsInterface(address account, bytes4 interfaceId) | |
internal | |
view | |
returns (bool) | |
{ | |
// query support of both ERC165 as per the spec and support of _interfaceId | |
return _supportsERC165(account) && | |
_supportsERC165Interface(account, interfaceId); | |
} | |
/** | |
* @notice Query if a contract implements interfaces, also checks support of ERC165 | |
* @param account The address of the contract to query for support of an interface | |
* @param interfaceIds A list of interface identifiers, as specified in ERC-165 | |
* @return true if the contract at account indicates support all interfaces in the | |
* interfaceIds list, false otherwise | |
* @dev Interface identification is specified in ERC-165. | |
*/ | |
function _supportsAllInterfaces(address account, bytes4[] interfaceIds) | |
internal | |
view | |
returns (bool) | |
{ | |
// query support of ERC165 itself | |
if (!_supportsERC165(account)) { | |
return false; | |
} | |
// query support of each interface in _interfaceIds | |
for (uint256 i = 0; i < interfaceIds.length; i++) { | |
if (!_supportsERC165Interface(account, interfaceIds[i])) { | |
return false; | |
} | |
} | |
// all interfaces supported | |
return true; | |
} | |
/** | |
* @notice Query if a contract implements an interface, does not check ERC165 support | |
* @param account The address of the contract to query for support of an interface | |
* @param interfaceId The interface identifier, as specified in ERC-165 | |
* @return true if the contract at account indicates support of the interface with | |
* identifier interfaceId, false otherwise | |
* @dev Assumes that account contains a contract that supports ERC165, otherwise | |
* the behavior of this method is undefined. This precondition can be checked | |
* with the `supportsERC165` method in this library. | |
* Interface identification is specified in ERC-165. | |
*/ | |
function _supportsERC165Interface(address account, bytes4 interfaceId) | |
private | |
view | |
returns (bool) | |
{ | |
// success determines whether the staticcall succeeded and result determines | |
// whether the contract at account indicates support of _interfaceId | |
(bool success, bool result) = _callERC165SupportsInterface( | |
account, interfaceId); | |
return (success && result); | |
} | |
/** | |
* @notice Calls the function with selector 0x01ffc9a7 (ERC165) and suppresses throw | |
* @param account The address of the contract to query for support of an interface | |
* @param interfaceId The interface identifier, as specified in ERC-165 | |
* @return success true if the STATICCALL succeeded, false otherwise | |
* @return result true if the STATICCALL succeeded and the contract at account | |
* indicates support of the interface with identifier interfaceId, false otherwise | |
*/ | |
function _callERC165SupportsInterface( | |
address account, | |
bytes4 interfaceId | |
) | |
private | |
view | |
returns (bool success, bool result) | |
{ | |
bytes memory encodedParams = abi.encodeWithSelector( | |
_InterfaceId_ERC165, | |
interfaceId | |
); | |
// solium-disable-next-line security/no-inline-assembly | |
assembly { | |
let encodedParams_data := add(0x20, encodedParams) | |
let encodedParams_size := mload(encodedParams) | |
let output := mload(0x40) // Find empty storage location using "free memory pointer" | |
mstore(output, 0x0) | |
success := staticcall( | |
30000, // 30k gas | |
account, // To addr | |
encodedParams_data, | |
encodedParams_size, | |
output, | |
0x20 // Outputs are 32 bytes long | |
) | |
result := mload(output) // Load the result | |
} | |
} | |
} | |
// File contracts/0.4.24/interfaces/IBeaconReportReceiver.sol | |
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi> | |
// SPDX-License-Identifier: GPL-3.0 | |
pragma solidity 0.4.24; | |
/** | |
* @title Interface defining a callback that the quorum will call on every quorum reached | |
*/ | |
interface IBeaconReportReceiver { | |
/** | |
* @notice Callback to be called by the oracle contract upon the quorum is reached | |
* @param _postTotalPooledEther total pooled ether on Lido right after the quorum value was reported | |
* @param _preTotalPooledEther total pooled ether on Lido right before the quorum value was reported | |
* @param _timeElapsed time elapsed in seconds between the last and the previous quorum | |
*/ | |
function processLidoOracleReport(uint256 _postTotalPooledEther, | |
uint256 _preTotalPooledEther, | |
uint256 _timeElapsed) external; | |
} | |
// File contracts/0.4.24/interfaces/ILido.sol | |
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi> | |
// SPDX-License-Identifier: GPL-3.0 | |
pragma solidity 0.4.24; | |
/** | |
* @title Liquid staking pool | |
* | |
* For the high-level description of the pool operation please refer to the paper. | |
* Pool manages withdrawal keys and fees. It receives ether submitted by users on the ETH 1 side | |
* and stakes it via the deposit_contract.sol contract. It doesn't hold ether on it's balance, | |
* only a small portion (buffer) of it. | |
* It also mints new tokens for rewards generated at the ETH 2.0 side. | |
* | |
* At the moment withdrawals are not possible in the beacon chain and there's no workaround. | |
* Pool will be upgraded to an actual implementation when withdrawals are enabled | |
* (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). | |
*/ | |
interface ILido { | |
function totalSupply() external view returns (uint256); | |
function getTotalShares() external view returns (uint256); | |
/** | |
* @notice Stop pool routine operations | |
*/ | |
function stop() external; | |
/** | |
* @notice Resume pool routine operations | |
*/ | |
function resume() external; | |
/** | |
* @notice Stops accepting new Ether to the protocol | |
* | |
* @dev While accepting new Ether is stopped, calls to the `submit` function, | |
* as well as to the default payable function, will revert. | |
* | |
* Emits `StakingPaused` event. | |
*/ | |
function pauseStaking() external; | |
/** | |
* @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) | |
* NB: Staking could be rate-limited by imposing a limit on the stake amount | |
* at each moment in time, see `setStakingLimit()` and `removeStakingLimit()` | |
* | |
* @dev Preserves staking limit if it was set previously | |
* | |
* Emits `StakingResumed` event | |
*/ | |
function resumeStaking() external; | |
/** | |
* @notice Sets the staking rate limit | |
* | |
* @dev Reverts if: | |
* - `_maxStakeLimit` == 0 | |
* - `_maxStakeLimit` >= 2^96 | |
* - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` | |
* - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) | |
* | |
* Emits `StakingLimitSet` event | |
* | |
* @param _maxStakeLimit max stake limit value | |
* @param _stakeLimitIncreasePerBlock stake limit increase per single block | |
*/ | |
function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external; | |
/** | |
* @notice Removes the staking rate limit | |
* | |
* Emits `StakingLimitRemoved` event | |
*/ | |
function removeStakingLimit() external; | |
/** | |
* @notice Check staking state: whether it's paused or not | |
*/ | |
function isStakingPaused() external view returns (bool); | |
/** | |
* @notice Returns how much Ether can be staked in the current block | |
* @dev Special return values: | |
* - 2^256 - 1 if staking is unlimited; | |
* - 0 if staking is paused or if limit is exhausted. | |
*/ | |
function getCurrentStakeLimit() external view returns (uint256); | |
/** | |
* @notice Returns full info about current stake limit params and state | |
* @dev Might be used for the advanced integration requests. | |
* @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) | |
* @return isStakingLimitSet whether the stake limit is set | |
* @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) | |
* @return maxStakeLimit max stake limit | |
* @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state | |
* @return prevStakeLimit previously reached stake limit | |
* @return prevStakeBlockNumber previously seen block number | |
*/ | |
function getStakeLimitFullInfo() external view returns ( | |
bool isStakingPaused, | |
bool isStakingLimitSet, | |
uint256 currentStakeLimit, | |
uint256 maxStakeLimit, | |
uint256 maxStakeLimitGrowthBlocks, | |
uint256 prevStakeLimit, | |
uint256 prevStakeBlockNumber | |
); | |
event Stopped(); | |
event Resumed(); | |
event StakingPaused(); | |
event StakingResumed(); | |
event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); | |
event StakingLimitRemoved(); | |
/** | |
* @notice Set Lido protocol contracts (oracle, treasury, insurance fund). | |
* @param _oracle oracle contract | |
* @param _treasury treasury contract | |
* @param _insuranceFund insurance fund contract | |
*/ | |
function setProtocolContracts( | |
address _oracle, | |
address _treasury, | |
address _insuranceFund | |
) external; | |
event ProtocolContactsSet(address oracle, address treasury, address insuranceFund); | |
/** | |
* @notice Set fee rate to `_feeBasisPoints` basis points. | |
* The fees are accrued when: | |
* - oracles report staking results (beacon chain balance increase) | |
* - validators gain execution layer rewards (priority fees and MEV) | |
* @param _feeBasisPoints Fee rate, in basis points | |
*/ | |
function setFee(uint16 _feeBasisPoints) external; | |
/** | |
* @notice Set fee distribution | |
* @param _treasuryFeeBasisPoints basis points go to the treasury, | |
* @param _insuranceFeeBasisPoints basis points go to the insurance fund, | |
* @param _operatorsFeeBasisPoints basis points go to node operators. | |
* @dev The sum has to be 10 000. | |
*/ | |
function setFeeDistribution( | |
uint16 _treasuryFeeBasisPoints, | |
uint16 _insuranceFeeBasisPoints, | |
uint16 _operatorsFeeBasisPoints | |
) external; | |
/** | |
* @notice Returns staking rewards fee rate | |
*/ | |
function getFee() external view returns (uint16 feeBasisPoints); | |
/** | |
* @notice Returns fee distribution proportion | |
*/ | |
function getFeeDistribution() external view returns ( | |
uint16 treasuryFeeBasisPoints, | |
uint16 insuranceFeeBasisPoints, | |
uint16 operatorsFeeBasisPoints | |
); | |
event FeeSet(uint16 feeBasisPoints); | |
event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); | |
/** | |
* @notice A payable function supposed to be called only by LidoExecutionLayerRewardsVault contract | |
* @dev We need a dedicated function because funds received by the default payable function | |
* are treated as a user deposit | |
*/ | |
function receiveELRewards() external payable; | |
// The amount of ETH withdrawn from LidoExecutionLayerRewardsVault contract to Lido contract | |
event ELRewardsReceived(uint256 amount); | |
/** | |
* @dev Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle report | |
* @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report | |
*/ | |
function setELRewardsWithdrawalLimit(uint16 _limitPoints) external; | |
// Percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report | |
event ELRewardsWithdrawalLimitSet(uint256 limitPoints); | |
/** | |
* @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` | |
* @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. | |
* @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs | |
*/ | |
function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external; | |
/** | |
* @notice Returns current credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched | |
*/ | |
function getWithdrawalCredentials() external view returns (bytes); | |
event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); | |
/** | |
* @dev Sets the address of LidoExecutionLayerRewardsVault contract | |
* @param _executionLayerRewardsVault Execution layer rewards vault contract address | |
*/ | |
function setELRewardsVault(address _executionLayerRewardsVault) external; | |
// The `executionLayerRewardsVault` was set as the execution layer rewards vault for Lido | |
event ELRewardsVaultSet(address executionLayerRewardsVault); | |
/** | |
* @notice Ether on the ETH 2.0 side reported by the oracle | |
* @param _epoch Epoch id | |
* @param _eth2balance Balance in wei on the ETH 2.0 side | |
*/ | |
function handleOracleReport(uint256 _epoch, uint256 _eth2balance) external; | |
// User functions | |
/** | |
* @notice Adds eth to the pool | |
* @return StETH Amount of StETH generated | |
*/ | |
function submit(address _referral) external payable returns (uint256 StETH); | |
// Records a deposit made by a user | |
event Submitted(address indexed sender, uint256 amount, address referral); | |
// The `amount` of ether was sent to the deposit_contract.deposit function | |
event Unbuffered(uint256 amount); | |
// Requested withdrawal of `etherAmount` to `pubkeyHash` on the ETH 2.0 side, `tokenAmount` burned by `sender`, | |
// `sentFromBuffer` was sent on the current Ethereum side. | |
event Withdrawal(address indexed sender, uint256 tokenAmount, uint256 sentFromBuffer, | |
bytes32 indexed pubkeyHash, uint256 etherAmount); | |
// Info functions | |
/** | |
* @notice Gets the amount of Ether controlled by the system | |
*/ | |
function getTotalPooledEther() external view returns (uint256); | |
/** | |
* @notice Gets the amount of Ether temporary buffered on this contract balance | |
*/ | |
function getBufferedEther() external view returns (uint256); | |
/** | |
* @notice Returns the key values related to Beacon-side | |
* @return depositedValidators - number of deposited validators | |
* @return beaconValidators - number of Lido's validators visible in the Beacon state, reported by oracles | |
* @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) | |
*/ | |
function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); | |
} | |
// File contracts/0.4.24/interfaces/ILidoOracle.sol | |
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi> | |
// SPDX-License-Identifier: GPL-3.0 | |
pragma solidity 0.4.24; | |
/** | |
* @title ETH 2.0 -> ETH oracle | |
* | |
* The goal of the oracle is to inform other parts of the system about balances controlled by the | |
* DAO on the ETH 2.0 side. The balances can go up because of reward accumulation and can go down | |
* because of slashing. | |
*/ | |
interface ILidoOracle { | |
event AllowedBeaconBalanceAnnualRelativeIncreaseSet(uint256 value); | |
event AllowedBeaconBalanceRelativeDecreaseSet(uint256 value); | |
event BeaconReportReceiverSet(address callback); | |
event MemberAdded(address member); | |
event MemberRemoved(address member); | |
event QuorumChanged(uint256 quorum); | |
event ExpectedEpochIdUpdated(uint256 epochId); | |
event BeaconSpecSet( | |
uint64 epochsPerFrame, | |
uint64 slotsPerEpoch, | |
uint64 secondsPerSlot, | |
uint64 genesisTime | |
); | |
event BeaconReported( | |
uint256 epochId, | |
uint128 beaconBalance, | |
uint128 beaconValidators, | |
address caller | |
); | |
event Completed( | |
uint256 epochId, | |
uint128 beaconBalance, | |
uint128 beaconValidators | |
); | |
event PostTotalShares( | |
uint256 postTotalPooledEther, | |
uint256 preTotalPooledEther, | |
uint256 timeElapsed, | |
uint256 totalShares); | |
event ContractVersionSet(uint256 version); | |
/** | |
* @notice Return the Lido contract address | |
*/ | |
function getLido() public view returns (ILido); | |
/** | |
* @notice Return the number of exactly the same reports needed to finalize the epoch | |
*/ | |
function getQuorum() public view returns (uint256); | |
/** | |
* @notice Return the upper bound of the reported balance possible increase in APR | |
*/ | |
function getAllowedBeaconBalanceAnnualRelativeIncrease() external view returns (uint256); | |
/** | |
* @notice Return the lower bound of the reported balance possible decrease | |
*/ | |
function getAllowedBeaconBalanceRelativeDecrease() external view returns (uint256); | |
/** | |
* @notice Set the upper bound of the reported balance possible increase in APR to `_value` | |
*/ | |
function setAllowedBeaconBalanceAnnualRelativeIncrease(uint256 _value) external; | |
/** | |
* @notice Set the lower bound of the reported balance possible decrease to `_value` | |
*/ | |
function setAllowedBeaconBalanceRelativeDecrease(uint256 _value) external; | |
/** | |
* @notice Return the receiver contract address to be called when the report is pushed to Lido | |
*/ | |
function getBeaconReportReceiver() external view returns (address); | |
/** | |
* @notice Set the receiver contract address to be called when the report is pushed to Lido | |
*/ | |
function setBeaconReportReceiver(address _addr) external; | |
/** | |
* @notice Return the current reporting bitmap, representing oracles who have already pushed | |
* their version of report during the expected epoch | |
*/ | |
function getCurrentOraclesReportStatus() external view returns (uint256); | |
/** | |
* @notice Return the current reporting array size | |
*/ | |
function getCurrentReportVariantsSize() external view returns (uint256); | |
/** | |
* @notice Return the current reporting array element with the given index | |
*/ | |
function getCurrentReportVariant(uint256 _index) | |
external | |
view | |
returns ( | |
uint64 beaconBalance, | |
uint32 beaconValidators, | |
uint16 count | |
); | |
/** | |
* @notice Return epoch that can be reported by oracles | |
*/ | |
function getExpectedEpochId() external view returns (uint256); | |
/** | |
* @notice Return the current oracle member committee list | |
*/ | |
function getOracleMembers() external view returns (address[]); | |
/** | |
* @notice Return the initialized version of this contract starting from 0 | |
*/ | |
function getVersion() external view returns (uint256); | |
/** | |
* @notice Return beacon specification data | |
*/ | |
function getBeaconSpec() | |
external | |
view | |
returns ( | |
uint64 epochsPerFrame, | |
uint64 slotsPerEpoch, | |
uint64 secondsPerSlot, | |
uint64 genesisTime | |
); | |
/** | |
* Updates beacon specification data | |
*/ | |
function setBeaconSpec( | |
uint64 _epochsPerFrame, | |
uint64 _slotsPerEpoch, | |
uint64 _secondsPerSlot, | |
uint64 _genesisTime | |
) | |
external; | |
/** | |
* Returns the epoch calculated from current timestamp | |
*/ | |
function getCurrentEpochId() external view returns (uint256); | |
/** | |
* @notice Return currently reportable epoch (the first epoch of the current frame) as well as | |
* its start and end times in seconds | |
*/ | |
function getCurrentFrame() | |
external | |
view | |
returns ( | |
uint256 frameEpochId, | |
uint256 frameStartTime, | |
uint256 frameEndTime | |
); | |
/** | |
* @notice Return last completed epoch | |
*/ | |
function getLastCompletedEpochId() external view returns (uint256); | |
/** | |
* @notice Report beacon balance and its change during the last frame | |
*/ | |
function getLastCompletedReportDelta() | |
external | |
view | |
returns ( | |
uint256 postTotalPooledEther, | |
uint256 preTotalPooledEther, | |
uint256 timeElapsed | |
); | |
/** | |
* @notice Initialize the contract (version 3 for now) from scratch | |
* @dev For details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md | |
* @param _lido Address of Lido contract | |
* @param _epochsPerFrame Number of epochs per frame | |
* @param _slotsPerEpoch Number of slots per epoch | |
* @param _secondsPerSlot Number of seconds per slot | |
* @param _genesisTime Genesis time | |
* @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) | |
* @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) | |
*/ | |
function initialize( | |
address _lido, | |
uint64 _epochsPerFrame, | |
uint64 _slotsPerEpoch, | |
uint64 _secondsPerSlot, | |
uint64 _genesisTime, | |
uint256 _allowedBeaconBalanceAnnualRelativeIncrease, | |
uint256 _allowedBeaconBalanceRelativeDecrease | |
) external; | |
/** | |
* @notice A function to finalize upgrade to v3 (from v1). Can be called only once | |
* @dev For more details see _initialize_v3() | |
*/ | |
function finalizeUpgrade_v3() external; | |
/** | |
* @notice Add `_member` to the oracle member committee list | |
*/ | |
function addOracleMember(address _member) external; | |
/** | |
* @notice Remove '_member` from the oracle member committee list | |
*/ | |
function removeOracleMember(address _member) external; | |
/** | |
* @notice Set the number of exactly the same reports needed to finalize the epoch to `_quorum` | |
*/ | |
function setQuorum(uint256 _quorum) external; | |
/** | |
* @notice Accept oracle committee member reports from the ETH 2.0 side | |
* @param _epochId Beacon chain epoch | |
* @param _beaconBalance Balance in gwei on the ETH 2.0 side (9-digit denomination) | |
* @param _beaconValidators Number of validators visible in this epoch | |
*/ | |
function reportBeacon(uint256 _epochId, uint64 _beaconBalance, uint32 _beaconValidators) external; | |
} | |
// File contracts/0.4.24/oracle/ReportUtils.sol | |
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi> | |
// SPDX-License-Identifier: GPL-3.0 | |
pragma solidity 0.4.24; | |
/** | |
* Utility functions for effectively storing reports within a single storage slot | |
* | |
* +00 | uint16 | count | 0..256 | number of reports received exactly like this | |
* +16 | uint32 | beaconValidators | 0..1e9 | number of Lido's validators in beacon chain | |
* +48 | uint64 | beaconBalance | 0..1e18 | total amout of their balance | |
* | |
* Note that the 'count' is the leftmost field here. Thus it is possible to apply addition | |
* operations to it when it is encoded, provided that you watch for the overflow. | |
*/ | |
library ReportUtils { | |
uint256 constant internal COUNT_OUTMASK = 0xFFFFFFFFFFFFFFFFFFFFFFFF0000; | |
function encode(uint64 beaconBalance, uint32 beaconValidators) internal pure returns (uint256) { | |
return uint256(beaconBalance) << 48 | uint256(beaconValidators) << 16; | |
} | |
function decode(uint256 value) internal pure returns (uint64 beaconBalance, uint32 beaconValidators) { | |
beaconBalance = uint64(value >> 48); | |
beaconValidators = uint32(value >> 16); | |
} | |
function decodeWithCount(uint256 value) | |
internal pure | |
returns ( | |
uint64 beaconBalance, | |
uint32 beaconValidators, | |
uint16 count | |
) { | |
beaconBalance = uint64(value >> 48); | |
beaconValidators = uint32(value >> 16); | |
count = uint16(value); | |
} | |
/// @notice Check if the given reports are different, not considering the counter of the first | |
function isDifferent(uint256 value, uint256 that) internal pure returns(bool) { | |
return (value & COUNT_OUTMASK) != that; | |
} | |
function getCount(uint256 value) internal pure returns(uint16) { | |
return uint16(value); | |
} | |
} | |
// File contracts/0.4.24/oracle/LidoOracle.sol | |
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi> | |
// SPDX-License-Identifier: GPL-3.0 | |
/* See contracts/COMPILERS.md */ | |
pragma solidity 0.4.24; | |
/** | |
* @title Implementation of an ETH 2.0 -> ETH oracle | |
* | |
* The goal of the oracle is to inform other parts of the system about balances controlled by the | |
* DAO on the ETH 2.0 side. The balances can go up because of reward accumulation and can go down | |
* because of slashing. | |
* | |
* The timeline is divided into consecutive frames. Every oracle member may push its report once | |
* per frame. When the equal reports reach the configurable 'quorum' value, this frame is | |
* considered finalized and the resulting report is pushed to Lido. | |
* | |
* Not all frames may come to a quorum. Oracles may report only to the first epoch of the frame and | |
* only if no quorum is reached for this epoch yet. | |
*/ | |
contract LidoOracle is ILidoOracle, AragonApp { | |
using SafeMath for uint256; | |
using ReportUtils for uint256; | |
using ERC165Checker for address; | |
struct BeaconSpec { | |
uint64 epochsPerFrame; | |
uint64 slotsPerEpoch; | |
uint64 secondsPerSlot; | |
uint64 genesisTime; | |
} | |
/// ACL | |
bytes32 constant public MANAGE_MEMBERS = | |
0xbf6336045918ae0015f4cdb3441a2fdbfaa4bcde6558c8692aac7f56c69fb067; // keccak256("MANAGE_MEMBERS") | |
bytes32 constant public MANAGE_QUORUM = | |
0xa5ffa9f45fa52c446078e834e1914561bd9c2ab1e833572d62af775da092ccbc; // keccak256("MANAGE_QUORUM") | |
bytes32 constant public SET_BEACON_SPEC = | |
0x16a273d48baf8111397316e6d961e6836913acb23b181e6c5fb35ec0bd2648fc; // keccak256("SET_BEACON_SPEC") | |
bytes32 constant public SET_REPORT_BOUNDARIES = | |
0x44adaee26c92733e57241cb0b26ffaa2d182ed7120ba3ecd7e0dce3635c01dc1; // keccak256("SET_REPORT_BOUNDARIES") | |
bytes32 constant public SET_BEACON_REPORT_RECEIVER = | |
0xe22a455f1bfbaf705ac3e891a64e156da92cb0b42cfc389158e6e82bd57f37be; // keccak256("SET_BEACON_REPORT_RECEIVER") | |
/// Maximum number of oracle committee members | |
uint256 public constant MAX_MEMBERS = 256; | |
/// Eth1 denomination is 18 digits, while Eth2 has 9 digits. Because we work with Eth2 | |
/// balances and to support old interfaces expecting eth1 format, we multiply by this | |
/// coefficient. | |
uint128 internal constant DENOMINATION_OFFSET = 1e9; | |
uint256 internal constant MEMBER_NOT_FOUND = uint256(-1); | |
/// Number of exactly the same reports needed to finalize the epoch | |
bytes32 internal constant QUORUM_POSITION = | |
0xd43b42c1ba05a1ab3c178623a49b2cdb55f000ec70b9ccdba5740b3339a7589e; // keccak256("lido.LidoOracle.quorum") | |
/// Address of the Lido contract | |
bytes32 internal constant LIDO_POSITION = | |
0xf6978a4f7e200f6d3a24d82d44c48bddabce399a3b8ec42a480ea8a2d5fe6ec5; // keccak256("lido.LidoOracle.lido") | |
/// Storage for the actual beacon chain specification | |
bytes32 internal constant BEACON_SPEC_POSITION = | |
0x805e82d53a51be3dfde7cfed901f1f96f5dad18e874708b082adb8841e8ca909; // keccak256("lido.LidoOracle.beaconSpec") | |
/// Version of the initialized contract data | |
/// NB: Contract versioning starts from 1. | |
/// The version stored in CONTRACT_VERSION_POSITION equals to | |
/// - 0 right after deployment when no initializer is invoked yet | |
/// - N after calling initialize() during deployment from scratch, where N is the current contract version | |
/// - N after upgrading contract from the previous version (after calling finalize_vN()) | |
bytes32 internal constant CONTRACT_VERSION_POSITION = | |
0x75be19a3f314d89bd1f84d30a6c84e2f1cd7afc7b6ca21876564c265113bb7e4; // keccak256("lido.LidoOracle.contractVersion") | |
/// Epoch that we currently collect reports | |
bytes32 internal constant EXPECTED_EPOCH_ID_POSITION = | |
0x65f1a0ee358a8a4000a59c2815dc768eb87d24146ca1ac5555cb6eb871aee915; // keccak256("lido.LidoOracle.expectedEpochId") | |
/// The bitmask of the oracle members that pushed their reports | |
bytes32 internal constant REPORTS_BITMASK_POSITION = | |
0xea6fa022365e4737a3bb52facb00ddc693a656fb51ffb2b4bd24fb85bdc888be; // keccak256("lido.LidoOracle.reportsBitMask") | |
/// Historic data about 2 last completed reports and their times | |
bytes32 internal constant POST_COMPLETED_TOTAL_POOLED_ETHER_POSITION = | |
0xaa8433b13d2b111d4f84f6f374bc7acbe20794944308876aa250fa9a73dc7f53; // keccak256("lido.LidoOracle.postCompletedTotalPooledEther") | |
bytes32 internal constant PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION = | |
0x1043177539af09a67d747435df3ff1155a64cd93a347daaac9132a591442d43e; // keccak256("lido.LidoOracle.preCompletedTotalPooledEther") | |
bytes32 internal constant LAST_COMPLETED_EPOCH_ID_POSITION = | |
0xdad15c0beecd15610092d84427258e369d2582df22869138b4c5265f049f574c; // keccak256("lido.LidoOracle.lastCompletedEpochId") | |
bytes32 internal constant TIME_ELAPSED_POSITION = | |
0x8fe323f4ecd3bf0497252a90142003855cc5125cee76a5b5ba5d508c7ec28c3a; // keccak256("lido.LidoOracle.timeElapsed") | |
/// Receiver address to be called when the report is pushed to Lido | |
bytes32 internal constant BEACON_REPORT_RECEIVER_POSITION = | |
0xb59039ed37776bc23c5d272e10b525a957a1dfad97f5006c84394b6b512c1564; // keccak256("lido.LidoOracle.beaconReportReceiver") | |
/// Upper bound of the reported balance possible increase in APR, controlled by the governance | |
bytes32 internal constant ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION = | |
0x613075ab597bed8ce2e18342385ce127d3e5298bc7a84e3db68dc64abd4811ac; // keccak256("lido.LidoOracle.allowedBeaconBalanceAnnualRelativeIncrease") | |
/// Lower bound of the reported balance possible decrease, controlled by the governance | |
/// | |
/// @notice When slashing happens, the balance may decrease at a much faster pace. Slashing are | |
/// one-time events that decrease the balance a fair amount - a few percent at a time in a | |
/// realistic scenario. Thus, instead of sanity check for an APR, we check if the plain relative | |
/// decrease is within bounds. Note that it's not annual value, its just one-jump value. | |
bytes32 internal constant ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION = | |
0x92ba7776ed6c5d13cf023555a94e70b823a4aebd56ed522a77345ff5cd8a9109; // keccak256("lido.LidoOracle.allowedBeaconBalanceDecrease") | |
/// This is a dead variable: it was used only in v1 and in upgrade v1 --> v2 | |
/// Just keep in mind that storage at this position is occupied but with no actual usage | |
bytes32 internal constant V1_LAST_REPORTED_EPOCH_ID_POSITION = | |
0xfe0250ed0c5d8af6526c6d133fccb8e5a55dd6b1aa6696ed0c327f8e517b5a94; // keccak256("lido.LidoOracle.lastReportedEpochId") | |
/// Contract structured storage | |
address[] private members; /// slot 0: oracle committee members | |
uint256[] private currentReportVariants; /// slot 1: reporting storage | |
/** | |
* @notice Return the Lido contract address | |
*/ | |
function getLido() public view returns (ILido) { | |
return ILido(LIDO_POSITION.getStorageAddress()); | |
} | |
/** | |
* @notice Return the number of exactly the same reports needed to finalize the epoch | |
*/ | |
function getQuorum() public view returns (uint256) { | |
return QUORUM_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Return the upper bound of the reported balance possible increase in APR | |
*/ | |
function getAllowedBeaconBalanceAnnualRelativeIncrease() external view returns (uint256) { | |
return ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Return the lower bound of the reported balance possible decrease | |
*/ | |
function getAllowedBeaconBalanceRelativeDecrease() external view returns (uint256) { | |
return ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Set the upper bound of the reported balance possible increase in APR to `_value` | |
*/ | |
function setAllowedBeaconBalanceAnnualRelativeIncrease(uint256 _value) external auth(SET_REPORT_BOUNDARIES) { | |
ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION.setStorageUint256(_value); | |
emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_value); | |
} | |
/** | |
* @notice Set the lower bound of the reported balance possible decrease to `_value` | |
*/ | |
function setAllowedBeaconBalanceRelativeDecrease(uint256 _value) external auth(SET_REPORT_BOUNDARIES) { | |
ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION.setStorageUint256(_value); | |
emit AllowedBeaconBalanceRelativeDecreaseSet(_value); | |
} | |
/** | |
* @notice Return the receiver contract address to be called when the report is pushed to Lido | |
*/ | |
function getBeaconReportReceiver() external view returns (address) { | |
return address(BEACON_REPORT_RECEIVER_POSITION.getStorageUint256()); | |
} | |
/** | |
* @notice Set the receiver contract address to `_addr` to be called when the report is pushed | |
* @dev Specify 0 to disable this functionality | |
*/ | |
function setBeaconReportReceiver(address _addr) external auth(SET_BEACON_REPORT_RECEIVER) { | |
if(_addr != address(0)) { | |
IBeaconReportReceiver iBeacon; | |
require( | |
_addr._supportsInterface(iBeacon.processLidoOracleReport.selector), | |
"BAD_BEACON_REPORT_RECEIVER" | |
); | |
} | |
BEACON_REPORT_RECEIVER_POSITION.setStorageUint256(uint256(_addr)); | |
emit BeaconReportReceiverSet(_addr); | |
} | |
/** | |
* @notice Return the current reporting bitmap, representing oracles who have already pushed | |
* their version of report during the expected epoch | |
* @dev Every oracle bit corresponds to the index of the oracle in the current members list | |
*/ | |
function getCurrentOraclesReportStatus() external view returns (uint256) { | |
return REPORTS_BITMASK_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Return the current reporting variants array size | |
*/ | |
function getCurrentReportVariantsSize() external view returns (uint256) { | |
return currentReportVariants.length; | |
} | |
/** | |
* @notice Return the current reporting array element with index `_index` | |
*/ | |
function getCurrentReportVariant(uint256 _index) | |
external | |
view | |
returns ( | |
uint64 beaconBalance, | |
uint32 beaconValidators, | |
uint16 count | |
) | |
{ | |
return currentReportVariants[_index].decodeWithCount(); | |
} | |
/** | |
* @notice Returns epoch that can be reported by oracles | |
*/ | |
function getExpectedEpochId() external view returns (uint256) { | |
return EXPECTED_EPOCH_ID_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Return the current oracle member committee list | |
*/ | |
function getOracleMembers() external view returns (address[]) { | |
return members; | |
} | |
/** | |
* @notice Return the initialized version of this contract starting from 0 | |
*/ | |
function getVersion() external view returns (uint256) { | |
return CONTRACT_VERSION_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Return beacon specification data | |
*/ | |
function getBeaconSpec() | |
external | |
view | |
returns ( | |
uint64 epochsPerFrame, | |
uint64 slotsPerEpoch, | |
uint64 secondsPerSlot, | |
uint64 genesisTime | |
) | |
{ | |
BeaconSpec memory beaconSpec = _getBeaconSpec(); | |
return ( | |
beaconSpec.epochsPerFrame, | |
beaconSpec.slotsPerEpoch, | |
beaconSpec.secondsPerSlot, | |
beaconSpec.genesisTime | |
); | |
} | |
/** | |
* @notice Update beacon specification data | |
*/ | |
function setBeaconSpec( | |
uint64 _epochsPerFrame, | |
uint64 _slotsPerEpoch, | |
uint64 _secondsPerSlot, | |
uint64 _genesisTime | |
) | |
external | |
auth(SET_BEACON_SPEC) | |
{ | |
_setBeaconSpec( | |
_epochsPerFrame, | |
_slotsPerEpoch, | |
_secondsPerSlot, | |
_genesisTime | |
); | |
} | |
/** | |
* @notice Return the epoch calculated from current timestamp | |
*/ | |
function getCurrentEpochId() external view returns (uint256) { | |
BeaconSpec memory beaconSpec = _getBeaconSpec(); | |
return _getCurrentEpochId(beaconSpec); | |
} | |
/** | |
* @notice Return currently reportable epoch (the first epoch of the current frame) as well as | |
* its start and end times in seconds | |
*/ | |
function getCurrentFrame() | |
external | |
view | |
returns ( | |
uint256 frameEpochId, | |
uint256 frameStartTime, | |
uint256 frameEndTime | |
) | |
{ | |
BeaconSpec memory beaconSpec = _getBeaconSpec(); | |
uint64 genesisTime = beaconSpec.genesisTime; | |
uint64 secondsPerEpoch = beaconSpec.secondsPerSlot * beaconSpec.slotsPerEpoch; | |
frameEpochId = _getFrameFirstEpochId(_getCurrentEpochId(beaconSpec), beaconSpec); | |
frameStartTime = frameEpochId * secondsPerEpoch + genesisTime; | |
frameEndTime = (frameEpochId + beaconSpec.epochsPerFrame) * secondsPerEpoch + genesisTime - 1; | |
} | |
/** | |
* @notice Return last completed epoch | |
*/ | |
function getLastCompletedEpochId() external view returns (uint256) { | |
return LAST_COMPLETED_EPOCH_ID_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Report beacon balance and its change during the last frame | |
*/ | |
function getLastCompletedReportDelta() | |
external | |
view | |
returns ( | |
uint256 postTotalPooledEther, | |
uint256 preTotalPooledEther, | |
uint256 timeElapsed | |
) | |
{ | |
postTotalPooledEther = POST_COMPLETED_TOTAL_POOLED_ETHER_POSITION.getStorageUint256(); | |
preTotalPooledEther = PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.getStorageUint256(); | |
timeElapsed = TIME_ELAPSED_POSITION.getStorageUint256(); | |
} | |
/** | |
* @notice Initialize the contract (version 3 for now) from scratch | |
* @dev For details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md | |
* @param _lido Address of Lido contract | |
* @param _epochsPerFrame Number of epochs per frame | |
* @param _slotsPerEpoch Number of slots per epoch | |
* @param _secondsPerSlot Number of seconds per slot | |
* @param _genesisTime Genesis time | |
* @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% increase) | |
* @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance instantaneous decrease (e.g. 500 means 5% decrease) | |
*/ | |
function initialize( | |
address _lido, | |
uint64 _epochsPerFrame, | |
uint64 _slotsPerEpoch, | |
uint64 _secondsPerSlot, | |
uint64 _genesisTime, | |
uint256 _allowedBeaconBalanceAnnualRelativeIncrease, | |
uint256 _allowedBeaconBalanceRelativeDecrease | |
) | |
external onlyInit | |
{ | |
assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert | |
// We consider storage state right after deployment (no initialize() called yet) as version 0 | |
// Initializations for v0 --> v1 | |
require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "BASE_VERSION_MUST_BE_ZERO"); | |
_setBeaconSpec( | |
_epochsPerFrame, | |
_slotsPerEpoch, | |
_secondsPerSlot, | |
_genesisTime | |
); | |
LIDO_POSITION.setStorageAddress(_lido); | |
QUORUM_POSITION.setStorageUint256(1); | |
emit QuorumChanged(1); | |
// Initializations for v1 --> v2 | |
ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION | |
.setStorageUint256(_allowedBeaconBalanceAnnualRelativeIncrease); | |
emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_allowedBeaconBalanceAnnualRelativeIncrease); | |
ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION | |
.setStorageUint256(_allowedBeaconBalanceRelativeDecrease); | |
emit AllowedBeaconBalanceRelativeDecreaseSet(_allowedBeaconBalanceRelativeDecrease); | |
// set expected epoch to the first epoch for the next frame | |
BeaconSpec memory beaconSpec = _getBeaconSpec(); | |
uint256 expectedEpoch = _getFrameFirstEpochId(0, beaconSpec) + beaconSpec.epochsPerFrame; | |
EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); | |
emit ExpectedEpochIdUpdated(expectedEpoch); | |
// Initializations for v2 --> v3 | |
_initialize_v3(); | |
// Needed to finish the Aragon part of initialization (otherwise auth() modifiers will fail) | |
initialized(); | |
} | |
/** | |
* @notice A function to finalize upgrade to v3 (from v1). Can be called only once | |
* @dev Value 2 in CONTRACT_VERSION_POSITION is skipped due to change in numbering | |
* For more details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md | |
*/ | |
function finalizeUpgrade_v3() external { | |
require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "WRONG_BASE_VERSION"); | |
_initialize_v3(); | |
} | |
/** | |
* @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number in storage | |
* @dev This function is introduced just to set in correspondence version number in storage, | |
* semantic version of the contract and number N used in naming of _initialize_nN/finalizeUpgrade_vN. | |
* NB, that thus version 2 is skipped | |
*/ | |
function _initialize_v3() internal { | |
CONTRACT_VERSION_POSITION.setStorageUint256(3); | |
emit ContractVersionSet(3); | |
} | |
/** | |
* @notice Add `_member` to the oracle member committee list | |
*/ | |
function addOracleMember(address _member) external auth(MANAGE_MEMBERS) { | |
require(address(0) != _member, "BAD_ARGUMENT"); | |
require(MEMBER_NOT_FOUND == _getMemberId(_member), "MEMBER_EXISTS"); | |
require(members.length < MAX_MEMBERS, "TOO_MANY_MEMBERS"); | |
members.push(_member); | |
emit MemberAdded(_member); | |
} | |
/** | |
* @notice Remove '_member` from the oracle member committee list | |
*/ | |
function removeOracleMember(address _member) external auth(MANAGE_MEMBERS) { | |
uint256 index = _getMemberId(_member); | |
require(index != MEMBER_NOT_FOUND, "MEMBER_NOT_FOUND"); | |
uint256 last = members.length - 1; | |
if (index != last) members[index] = members[last]; | |
members.length--; | |
emit MemberRemoved(_member); | |
// delete the data for the last epoch, let remained oracles report it again | |
REPORTS_BITMASK_POSITION.setStorageUint256(0); | |
delete currentReportVariants; | |
} | |
/** | |
* @notice Set the number of exactly the same reports needed to finalize the epoch to `_quorum` | |
*/ | |
function setQuorum(uint256 _quorum) external auth(MANAGE_QUORUM) { | |
require(0 != _quorum, "QUORUM_WONT_BE_MADE"); | |
uint256 oldQuorum = QUORUM_POSITION.getStorageUint256(); | |
QUORUM_POSITION.setStorageUint256(_quorum); | |
emit QuorumChanged(_quorum); | |
// If the quorum value lowered, check existing reports whether it is time to push | |
if (oldQuorum > _quorum) { | |
(bool isQuorum, uint256 report) = _getQuorumReport(_quorum); | |
if (isQuorum) { | |
(uint64 beaconBalance, uint32 beaconValidators) = report.decode(); | |
_push( | |
EXPECTED_EPOCH_ID_POSITION.getStorageUint256(), | |
DENOMINATION_OFFSET * uint128(beaconBalance), | |
beaconValidators, | |
_getBeaconSpec() | |
); | |
} | |
} | |
} | |
/** | |
* @notice Accept oracle committee member reports from the ETH 2.0 side | |
* @param _epochId Beacon chain epoch | |
* @param _beaconBalance Balance in gwei on the ETH 2.0 side (9-digit denomination) | |
* @param _beaconValidators Number of validators visible in this epoch | |
*/ | |
function reportBeacon(uint256 _epochId, uint64 _beaconBalance, uint32 _beaconValidators) external { | |
BeaconSpec memory beaconSpec = _getBeaconSpec(); | |
uint256 expectedEpoch = EXPECTED_EPOCH_ID_POSITION.getStorageUint256(); | |
require(_epochId >= expectedEpoch, "EPOCH_IS_TOO_OLD"); | |
// if expected epoch has advanced, check that this is the first epoch of the current frame | |
// and clear the last unsuccessful reporting | |
if (_epochId > expectedEpoch) { | |
require(_epochId == _getFrameFirstEpochId(_getCurrentEpochId(beaconSpec), beaconSpec), "UNEXPECTED_EPOCH"); | |
_clearReportingAndAdvanceTo(_epochId); | |
} | |
uint128 beaconBalanceEth1 = DENOMINATION_OFFSET * uint128(_beaconBalance); | |
emit BeaconReported(_epochId, beaconBalanceEth1, _beaconValidators, msg.sender); | |
// make sure the oracle is from members list and has not yet voted | |
uint256 index = _getMemberId(msg.sender); | |
require(index != MEMBER_NOT_FOUND, "MEMBER_NOT_FOUND"); | |
uint256 bitMask = REPORTS_BITMASK_POSITION.getStorageUint256(); | |
uint256 mask = 1 << index; | |
require(bitMask & mask == 0, "ALREADY_SUBMITTED"); | |
REPORTS_BITMASK_POSITION.setStorageUint256(bitMask | mask); | |
// push this report to the matching kind | |
uint256 report = ReportUtils.encode(_beaconBalance, _beaconValidators); | |
uint256 quorum = getQuorum(); | |
uint256 i = 0; | |
// iterate on all report variants we already have, limited by the oracle members maximum | |
while (i < currentReportVariants.length && currentReportVariants[i].isDifferent(report)) ++i; | |
if (i < currentReportVariants.length) { | |
if (currentReportVariants[i].getCount() + 1 >= quorum) { | |
_push(_epochId, beaconBalanceEth1, _beaconValidators, beaconSpec); | |
} else { | |
++currentReportVariants[i]; // increment report counter, see ReportUtils for details | |
} | |
} else { | |
if (quorum == 1) { | |
_push(_epochId, beaconBalanceEth1, _beaconValidators, beaconSpec); | |
} else { | |
currentReportVariants.push(report + 1); | |
} | |
} | |
} | |
/** | |
* @notice Return beacon specification data | |
*/ | |
function _getBeaconSpec() | |
internal | |
view | |
returns (BeaconSpec memory beaconSpec) | |
{ | |
uint256 data = BEACON_SPEC_POSITION.getStorageUint256(); | |
beaconSpec.epochsPerFrame = uint64(data >> 192); | |
beaconSpec.slotsPerEpoch = uint64(data >> 128); | |
beaconSpec.secondsPerSlot = uint64(data >> 64); | |
beaconSpec.genesisTime = uint64(data); | |
return beaconSpec; | |
} | |
/** | |
* @notice Return whether the `_quorum` is reached and the final report | |
*/ | |
function _getQuorumReport(uint256 _quorum) internal view returns (bool isQuorum, uint256 report) { | |
// check most frequent cases first: all reports are the same or no reports yet | |
if (currentReportVariants.length == 1) { | |
return (currentReportVariants[0].getCount() >= _quorum, currentReportVariants[0]); | |
} else if (currentReportVariants.length == 0) { | |
return (false, 0); | |
} | |
// if more than 2 kind of reports exist, choose the most frequent | |
uint256 maxind = 0; | |
uint256 repeat = 0; | |
uint16 maxval = 0; | |
uint16 cur = 0; | |
for (uint256 i = 0; i < currentReportVariants.length; ++i) { | |
cur = currentReportVariants[i].getCount(); | |
if (cur >= maxval) { | |
if (cur == maxval) { | |
++repeat; | |
} else { | |
maxind = i; | |
maxval = cur; | |
repeat = 0; | |
} | |
} | |
} | |
return (maxval >= _quorum && repeat == 0, currentReportVariants[maxind]); | |
} | |
/** | |
* @notice Set beacon specification data | |
*/ | |
function _setBeaconSpec( | |
uint64 _epochsPerFrame, | |
uint64 _slotsPerEpoch, | |
uint64 _secondsPerSlot, | |
uint64 _genesisTime | |
) | |
internal | |
{ | |
require(_epochsPerFrame > 0, "BAD_EPOCHS_PER_FRAME"); | |
require(_slotsPerEpoch > 0, "BAD_SLOTS_PER_EPOCH"); | |
require(_secondsPerSlot > 0, "BAD_SECONDS_PER_SLOT"); | |
require(_genesisTime > 0, "BAD_GENESIS_TIME"); | |
uint256 data = ( | |
uint256(_epochsPerFrame) << 192 | | |
uint256(_slotsPerEpoch) << 128 | | |
uint256(_secondsPerSlot) << 64 | | |
uint256(_genesisTime) | |
); | |
BEACON_SPEC_POSITION.setStorageUint256(data); | |
emit BeaconSpecSet( | |
_epochsPerFrame, | |
_slotsPerEpoch, | |
_secondsPerSlot, | |
_genesisTime); | |
} | |
/** | |
* @notice Push the given report to Lido and performs accompanying accounting | |
* @param _epochId Beacon chain epoch, proven to be >= expected epoch and <= current epoch | |
* @param _beaconBalanceEth1 Validators balance in eth1 (18-digit denomination) | |
* @param _beaconSpec current beacon specification data | |
*/ | |
function _push( | |
uint256 _epochId, | |
uint128 _beaconBalanceEth1, | |
uint128 _beaconValidators, | |
BeaconSpec memory _beaconSpec | |
) | |
internal | |
{ | |
emit Completed(_epochId, _beaconBalanceEth1, _beaconValidators); | |
// now this frame is completed, so the expected epoch should be advanced to the first epoch | |
// of the next frame | |
_clearReportingAndAdvanceTo(_epochId + _beaconSpec.epochsPerFrame); | |
// report to the Lido and collect stats | |
ILido lido = getLido(); | |
uint256 prevTotalPooledEther = lido.totalSupply(); | |
lido.handleOracleReport(_beaconValidators, _beaconBalanceEth1); | |
uint256 postTotalPooledEther = lido.totalSupply(); | |
PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.setStorageUint256(prevTotalPooledEther); | |
POST_COMPLETED_TOTAL_POOLED_ETHER_POSITION.setStorageUint256(postTotalPooledEther); | |
uint256 timeElapsed = (_epochId - LAST_COMPLETED_EPOCH_ID_POSITION.getStorageUint256()) * | |
_beaconSpec.slotsPerEpoch * _beaconSpec.secondsPerSlot; | |
TIME_ELAPSED_POSITION.setStorageUint256(timeElapsed); | |
LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(_epochId); | |
// rollback on boundaries violation | |
_reportSanityChecks(postTotalPooledEther, prevTotalPooledEther, timeElapsed); | |
// emit detailed statistics and call the quorum delegate with this data | |
emit PostTotalShares(postTotalPooledEther, prevTotalPooledEther, timeElapsed, lido.getTotalShares()); | |
IBeaconReportReceiver receiver = IBeaconReportReceiver(BEACON_REPORT_RECEIVER_POSITION.getStorageUint256()); | |
if (address(receiver) != address(0)) { | |
receiver.processLidoOracleReport(postTotalPooledEther, prevTotalPooledEther, timeElapsed); | |
} | |
} | |
/** | |
* @notice Remove the current reporting progress and advances to accept the later epoch `_epochId` | |
*/ | |
function _clearReportingAndAdvanceTo(uint256 _epochId) internal { | |
REPORTS_BITMASK_POSITION.setStorageUint256(0); | |
EXPECTED_EPOCH_ID_POSITION.setStorageUint256(_epochId); | |
delete currentReportVariants; | |
emit ExpectedEpochIdUpdated(_epochId); | |
} | |
/** | |
* @notice Performs logical consistency check of the Lido changes as the result of reports push | |
* @dev To make oracles less dangerous, we limit rewards report by 10% _annual_ increase and 5% | |
* _instant_ decrease in stake, with both values configurable by the governance in case of | |
* extremely unusual circumstances. | |
**/ | |
function _reportSanityChecks( | |
uint256 _postTotalPooledEther, | |
uint256 _preTotalPooledEther, | |
uint256 _timeElapsed) | |
internal | |
view | |
{ | |
if (_postTotalPooledEther >= _preTotalPooledEther) { | |
// increase = _postTotalPooledEther - _preTotalPooledEther, | |
// relativeIncrease = increase / _preTotalPooledEther, | |
// annualRelativeIncrease = relativeIncrease / (timeElapsed / 365 days), | |
// annualRelativeIncreaseBp = annualRelativeIncrease * 10000, in basis points 0.01% (1e-4) | |
uint256 allowedAnnualRelativeIncreaseBp = | |
ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION.getStorageUint256(); | |
// check that annualRelativeIncreaseBp <= allowedAnnualRelativeIncreaseBp | |
require(uint256(10000 * 365 days).mul(_postTotalPooledEther - _preTotalPooledEther) <= | |
allowedAnnualRelativeIncreaseBp.mul(_preTotalPooledEther).mul(_timeElapsed), | |
"ALLOWED_BEACON_BALANCE_INCREASE"); | |
} else { | |
// decrease = _preTotalPooledEther - _postTotalPooledEther | |
// relativeDecrease = decrease / _preTotalPooledEther | |
// relativeDecreaseBp = relativeDecrease * 10000, in basis points 0.01% (1e-4) | |
uint256 allowedRelativeDecreaseBp = | |
ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION.getStorageUint256(); | |
// check that relativeDecreaseBp <= allowedRelativeDecreaseBp | |
require(uint256(10000).mul(_preTotalPooledEther - _postTotalPooledEther) <= | |
allowedRelativeDecreaseBp.mul(_preTotalPooledEther), | |
"ALLOWED_BEACON_BALANCE_DECREASE"); | |
} | |
} | |
/** | |
* @notice Return `_member` index in the members list or MEMBER_NOT_FOUND | |
*/ | |
function _getMemberId(address _member) internal view returns (uint256) { | |
uint256 length = members.length; | |
for (uint256 i = 0; i < length; ++i) { | |
if (members[i] == _member) { | |
return i; | |
} | |
} | |
return MEMBER_NOT_FOUND; | |
} | |
/** | |
* @notice Return the epoch calculated from current timestamp | |
*/ | |
function _getCurrentEpochId(BeaconSpec memory _beaconSpec) internal view returns (uint256) { | |
return (_getTime() - _beaconSpec.genesisTime) / (_beaconSpec.slotsPerEpoch * _beaconSpec.secondsPerSlot); | |
} | |
/** | |
* @notice Return the first epoch of the frame that `_epochId` belongs to | |
*/ | |
function _getFrameFirstEpochId(uint256 _epochId, BeaconSpec memory _beaconSpec) internal view returns (uint256) { | |
return _epochId / _beaconSpec.epochsPerFrame * _beaconSpec.epochsPerFrame; | |
} | |
/** | |
* @notice Return the current timestamp | |
*/ | |
function _getTime() internal view returns (uint256) { | |
return block.timestamp; // solhint-disable-line not-rely-on-time | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment