Transaction Hash:
Block:
14512051 at Apr-03-2022 08:14:49 AM +UTC
Transaction Fee:
0.0020704769271306 ETH
$4.41
Gas Used:
67,850 Gas / 30.515503716 Gwei
Emitted Events:
| 163 |
AppProxyUpgradeable.0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925( 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x0000000000000000000000009cd7f5d86cc7a01936182bdcc5e455b94acc4ee8, 0x000000000000000000000000dc24316b9ae028f1497c275eb9192a3ea0f67022, ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x9CD7F5D8...94acC4EE8 |
0.007720364561157784 Eth
Nonce: 4
|
0.005649887634027184 Eth
Nonce: 5
| 0.0020704769271306 | ||
| 0xae7ab965...312D7fE84 | |||||
|
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 1,027.297498301213265528 Eth | 1,027.297579116871347878 Eth | 0.00008081565808235 |
Execution Trace
AppProxyUpgradeable.095ea7b3( )
KernelProxy.be00bbd8( )-
Kernel.getApp( _namespace=F1F3EB40F5BC1AD1344716CED8B8A0431D840B5783AEA1FD01786BC26F35AC0F, _appId=3CA7C3E38968823CCB4C78EA688DF41356F182AE1D159E4EE608D30D68CEF320 ) => ( 0xC7B5aF82B05Eb3b64F12241B04B2cF14469E39F7 )
-
-
Lido.approve( _spender=0xDC24316b9AE028F1497c275EB9192a3Ea0f67022, _amount=115792089237316195423570985008687907853269984665640564039457584007913129639935 ) => ( True )
File 1 of 4: AppProxyUpgradeable
File 2 of 4: KernelProxy
File 3 of 4: Kernel
File 4 of 4: Lido
// File: contracts/common/UnstructuredStorage.sol
/*
* SPDX-License-Identitifer: 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: contracts/acl/IACL.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/IVaultRecoverable.sol
/*
* SPDX-License-Identitifer: 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: contracts/kernel/IKernel.sol
/*
* SPDX-License-Identitifer: 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: contracts/apps/AppStorage.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/IsContract.sol
/*
* SPDX-License-Identitifer: 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: contracts/lib/misc/ERCProxy.sol
/*
* SPDX-License-Identitifer: MIT
*/
pragma solidity ^0.4.24;
contract ERCProxy {
uint256 internal constant FORWARDING = 1;
uint256 internal constant UPGRADEABLE = 2;
function proxyType() public pure returns (uint256 proxyTypeId);
function implementation() public view returns (address codeAddr);
}
// File: contracts/common/DelegateProxy.sol
pragma solidity 0.4.24;
contract DelegateProxy is ERCProxy, IsContract {
uint256 internal constant FWD_GAS_LIMIT = 10000;
/**
* @dev Performs a delegatecall and returns whatever the delegatecall returned (entire context execution will return!)
* @param _dst Destination address to perform the delegatecall
* @param _calldata Calldata for the delegatecall
*/
function delegatedFwd(address _dst, bytes _calldata) internal {
require(isContract(_dst));
uint256 fwdGasLimit = FWD_GAS_LIMIT;
assembly {
let result := delegatecall(sub(gas, fwdGasLimit), _dst, add(_calldata, 0x20), mload(_calldata), 0, 0)
let size := returndatasize
let ptr := mload(0x40)
returndatacopy(ptr, 0, size)
// revert instead of invalid() bc if the underlying call failed with invalid() it already wasted gas.
// if the call returned error data, forward it
switch result case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
// File: contracts/common/DepositableStorage.sol
pragma solidity 0.4.24;
contract DepositableStorage {
using UnstructuredStorage for bytes32;
// keccak256("aragonOS.depositableStorage.depositable")
bytes32 internal constant DEPOSITABLE_POSITION = 0x665fd576fbbe6f247aff98f5c94a561e3f71ec2d3c988d56f12d342396c50cea;
function isDepositable() public view returns (bool) {
return DEPOSITABLE_POSITION.getStorageBool();
}
function setDepositable(bool _depositable) internal {
DEPOSITABLE_POSITION.setStorageBool(_depositable);
}
}
// File: contracts/common/DepositableDelegateProxy.sol
pragma solidity 0.4.24;
contract DepositableDelegateProxy is DepositableStorage, DelegateProxy {
event ProxyDeposit(address sender, uint256 value);
function () external payable {
uint256 forwardGasThreshold = FWD_GAS_LIMIT;
bytes32 isDepositablePosition = DEPOSITABLE_POSITION;
// Optimized assembly implementation to prevent EIP-1884 from breaking deposits, reference code in Solidity:
// https://github.com/aragon/aragonOS/blob/v4.2.1/contracts/common/DepositableDelegateProxy.sol#L10-L20
assembly {
// Continue only if the gas left is lower than the threshold for forwarding to the implementation code,
// otherwise continue outside of the assembly block.
if lt(gas, forwardGasThreshold) {
// Only accept the deposit and emit an event if all of the following are true:
// the proxy accepts deposits (isDepositable), msg.data.length == 0, and msg.value > 0
if and(and(sload(isDepositablePosition), iszero(calldatasize)), gt(callvalue, 0)) {
// Equivalent Solidity code for emitting the event:
// emit ProxyDeposit(msg.sender, msg.value);
let logData := mload(0x40) // free memory pointer
mstore(logData, caller) // add 'msg.sender' to the log data (first event param)
mstore(add(logData, 0x20), callvalue) // add 'msg.value' to the log data (second event param)
// Emit an event with one topic to identify the event: keccak256('ProxyDeposit(address,uint256)') = 0x15ee...dee1
log1(logData, 0x40, 0x15eeaa57c7bd188c1388020bcadc2c436ec60d647d36ef5b9eb3c742217ddee1)
stop() // Stop. Exits execution context
}
// If any of above checks failed, revert the execution (if ETH was sent, it is returned to the sender)
revert(0, 0)
}
}
address target = implementation();
delegatedFwd(target, msg.data);
}
}
// File: contracts/kernel/KernelConstants.sol
/*
* SPDX-License-Identitifer: 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: contracts/apps/AppProxyBase.sol
pragma solidity 0.4.24;
contract AppProxyBase is AppStorage, DepositableDelegateProxy, KernelNamespaceConstants {
/**
* @dev Initialize AppProxy
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload) public {
setKernel(_kernel);
setAppId(_appId);
// Implicit check that kernel is actually a Kernel
// The EVM doesn't actually provide a way for us to make sure, but we can force a revert to
// occur if the kernel is set to 0x0 or a non-code address when we try to call a method on
// it.
address appCode = getAppBase(_appId);
// If initialize payload is provided, it will be executed
if (_initializePayload.length > 0) {
require(isContract(appCode));
// Cannot make delegatecall as a delegateproxy.delegatedFwd as it
// returns ending execution context and halts contract deployment
require(appCode.delegatecall(_initializePayload));
}
}
function getAppBase(bytes32 _appId) internal view returns (address) {
return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, _appId);
}
}
// File: contracts/apps/AppProxyUpgradeable.sol
pragma solidity 0.4.24;
contract AppProxyUpgradeable is AppProxyBase {
/**
* @dev Initialize AppProxyUpgradeable (makes it an upgradeable Aragon app)
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload)
AppProxyBase(_kernel, _appId, _initializePayload)
public // solium-disable-line visibility-first
{
// solium-disable-previous-line no-empty-blocks
}
/**
* @dev ERC897, the address the proxy would delegate calls to
*/
function implementation() public view returns (address) {
return getAppBase(appId());
}
/**
* @dev ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy
*/
function proxyType() public pure returns (uint256 proxyTypeId) {
return UPGRADEABLE;
}
}File 2 of 4: KernelProxy
/**
*Submitted for verification at Etherscan.io on 2020-02-06
*/
// File: contracts/acl/IACL.sol
/*
* 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: contracts/common/IVaultRecoverable.sol
/*
* 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: contracts/kernel/IKernel.sol
/*
* 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: contracts/kernel/KernelConstants.sol
/*
* 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: contracts/kernel/KernelStorage.sol
pragma solidity 0.4.24;
contract KernelStorage {
// namespace => app id => address
mapping (bytes32 => mapping (bytes32 => address)) public apps;
bytes32 public recoveryVaultAppId;
}
// File: contracts/acl/ACLSyntaxSugar.sol
/*
* 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: contracts/common/ConversionHelpers.sol
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: contracts/common/IsContract.sol
/*
* 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: contracts/common/Uint256Helpers.sol
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: contracts/common/TimeHelpers.sol
/*
* 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: contracts/common/UnstructuredStorage.sol
/*
* 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: contracts/common/Initializable.sol
/*
* 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: contracts/common/Petrifiable.sol
/*
* 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: contracts/lib/token/ERC20.sol
// 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: contracts/common/EtherTokenConstant.sol
/*
* 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: contracts/common/SafeERC20.sol
// 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: contracts/common/VaultRecoverable.sol
/*
* 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: contracts/apps/AppStorage.sol
/*
* 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: contracts/lib/misc/ERCProxy.sol
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
contract ERCProxy {
uint256 internal constant FORWARDING = 1;
uint256 internal constant UPGRADEABLE = 2;
function proxyType() public pure returns (uint256 proxyTypeId);
function implementation() public view returns (address codeAddr);
}
// File: contracts/common/DelegateProxy.sol
pragma solidity 0.4.24;
contract DelegateProxy is ERCProxy, IsContract {
uint256 internal constant FWD_GAS_LIMIT = 10000;
/**
* @dev Performs a delegatecall and returns whatever the delegatecall returned (entire context execution will return!)
* @param _dst Destination address to perform the delegatecall
* @param _calldata Calldata for the delegatecall
*/
function delegatedFwd(address _dst, bytes _calldata) internal {
require(isContract(_dst));
uint256 fwdGasLimit = FWD_GAS_LIMIT;
assembly {
let result := delegatecall(sub(gas, fwdGasLimit), _dst, add(_calldata, 0x20), mload(_calldata), 0, 0)
let size := returndatasize
let ptr := mload(0x40)
returndatacopy(ptr, 0, size)
// revert instead of invalid() bc if the underlying call failed with invalid() it already wasted gas.
// if the call returned error data, forward it
switch result case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
// File: contracts/common/DepositableStorage.sol
pragma solidity 0.4.24;
contract DepositableStorage {
using UnstructuredStorage for bytes32;
// keccak256("aragonOS.depositableStorage.depositable")
bytes32 internal constant DEPOSITABLE_POSITION = 0x665fd576fbbe6f247aff98f5c94a561e3f71ec2d3c988d56f12d342396c50cea;
function isDepositable() public view returns (bool) {
return DEPOSITABLE_POSITION.getStorageBool();
}
function setDepositable(bool _depositable) internal {
DEPOSITABLE_POSITION.setStorageBool(_depositable);
}
}
// File: contracts/common/DepositableDelegateProxy.sol
pragma solidity 0.4.24;
contract DepositableDelegateProxy is DepositableStorage, DelegateProxy {
event ProxyDeposit(address sender, uint256 value);
function () external payable {
uint256 forwardGasThreshold = FWD_GAS_LIMIT;
bytes32 isDepositablePosition = DEPOSITABLE_POSITION;
// Optimized assembly implementation to prevent EIP-1884 from breaking deposits, reference code in Solidity:
// https://github.com/aragon/aragonOS/blob/v4.2.1/contracts/common/DepositableDelegateProxy.sol#L10-L20
assembly {
// Continue only if the gas left is lower than the threshold for forwarding to the implementation code,
// otherwise continue outside of the assembly block.
if lt(gas, forwardGasThreshold) {
// Only accept the deposit and emit an event if all of the following are true:
// the proxy accepts deposits (isDepositable), msg.data.length == 0, and msg.value > 0
if and(and(sload(isDepositablePosition), iszero(calldatasize)), gt(callvalue, 0)) {
// Equivalent Solidity code for emitting the event:
// emit ProxyDeposit(msg.sender, msg.value);
let logData := mload(0x40) // free memory pointer
mstore(logData, caller) // add 'msg.sender' to the log data (first event param)
mstore(add(logData, 0x20), callvalue) // add 'msg.value' to the log data (second event param)
// Emit an event with one topic to identify the event: keccak256('ProxyDeposit(address,uint256)') = 0x15ee...dee1
log1(logData, 0x40, 0x15eeaa57c7bd188c1388020bcadc2c436ec60d647d36ef5b9eb3c742217ddee1)
stop() // Stop. Exits execution context
}
// If any of above checks failed, revert the execution (if ETH was sent, it is returned to the sender)
revert(0, 0)
}
}
address target = implementation();
delegatedFwd(target, msg.data);
}
}
// File: contracts/apps/AppProxyBase.sol
pragma solidity 0.4.24;
contract AppProxyBase is AppStorage, DepositableDelegateProxy, KernelNamespaceConstants {
/**
* @dev Initialize AppProxy
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload) public {
setKernel(_kernel);
setAppId(_appId);
// Implicit check that kernel is actually a Kernel
// The EVM doesn't actually provide a way for us to make sure, but we can force a revert to
// occur if the kernel is set to 0x0 or a non-code address when we try to call a method on
// it.
address appCode = getAppBase(_appId);
// If initialize payload is provided, it will be executed
if (_initializePayload.length > 0) {
require(isContract(appCode));
// Cannot make delegatecall as a delegateproxy.delegatedFwd as it
// returns ending execution context and halts contract deployment
require(appCode.delegatecall(_initializePayload));
}
}
function getAppBase(bytes32 _appId) internal view returns (address) {
return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, _appId);
}
}
// File: contracts/apps/AppProxyUpgradeable.sol
pragma solidity 0.4.24;
contract AppProxyUpgradeable is AppProxyBase {
/**
* @dev Initialize AppProxyUpgradeable (makes it an upgradeable Aragon app)
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload)
AppProxyBase(_kernel, _appId, _initializePayload)
public // solium-disable-line visibility-first
{
// solium-disable-previous-line no-empty-blocks
}
/**
* @dev ERC897, the address the proxy would delegate calls to
*/
function implementation() public view returns (address) {
return getAppBase(appId());
}
/**
* @dev ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy
*/
function proxyType() public pure returns (uint256 proxyTypeId) {
return UPGRADEABLE;
}
}
// File: contracts/apps/AppProxyPinned.sol
pragma solidity 0.4.24;
contract AppProxyPinned is IsContract, AppProxyBase {
using UnstructuredStorage for bytes32;
// keccak256("aragonOS.appStorage.pinnedCode")
bytes32 internal constant PINNED_CODE_POSITION = 0xdee64df20d65e53d7f51cb6ab6d921a0a6a638a91e942e1d8d02df28e31c038e;
/**
* @dev Initialize AppProxyPinned (makes it an un-upgradeable Aragon app)
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload)
AppProxyBase(_kernel, _appId, _initializePayload)
public // solium-disable-line visibility-first
{
setPinnedCode(getAppBase(_appId));
require(isContract(pinnedCode()));
}
/**
* @dev ERC897, the address the proxy would delegate calls to
*/
function implementation() public view returns (address) {
return pinnedCode();
}
/**
* @dev ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy
*/
function proxyType() public pure returns (uint256 proxyTypeId) {
return FORWARDING;
}
function setPinnedCode(address _pinnedCode) internal {
PINNED_CODE_POSITION.setStorageAddress(_pinnedCode);
}
function pinnedCode() internal view returns (address) {
return PINNED_CODE_POSITION.getStorageAddress();
}
}
// File: contracts/factory/AppProxyFactory.sol
pragma solidity 0.4.24;
contract AppProxyFactory {
event NewAppProxy(address proxy, bool isUpgradeable, bytes32 appId);
/**
* @notice Create a new upgradeable app instance on `_kernel` with identifier `_appId`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @return AppProxyUpgradeable
*/
function newAppProxy(IKernel _kernel, bytes32 _appId) public returns (AppProxyUpgradeable) {
return newAppProxy(_kernel, _appId, new bytes(0));
}
/**
* @notice Create a new upgradeable app instance on `_kernel` with identifier `_appId` and initialization payload `_initializePayload`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @return AppProxyUpgradeable
*/
function newAppProxy(IKernel _kernel, bytes32 _appId, bytes _initializePayload) public returns (AppProxyUpgradeable) {
AppProxyUpgradeable proxy = new AppProxyUpgradeable(_kernel, _appId, _initializePayload);
emit NewAppProxy(address(proxy), true, _appId);
return proxy;
}
/**
* @notice Create a new pinned app instance on `_kernel` with identifier `_appId`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @return AppProxyPinned
*/
function newAppProxyPinned(IKernel _kernel, bytes32 _appId) public returns (AppProxyPinned) {
return newAppProxyPinned(_kernel, _appId, new bytes(0));
}
/**
* @notice Create a new pinned app instance on `_kernel` with identifier `_appId` and initialization payload `_initializePayload`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @param _initializePayload Proxy initialization payload
* @return AppProxyPinned
*/
function newAppProxyPinned(IKernel _kernel, bytes32 _appId, bytes _initializePayload) public returns (AppProxyPinned) {
AppProxyPinned proxy = new AppProxyPinned(_kernel, _appId, _initializePayload);
emit NewAppProxy(address(proxy), false, _appId);
return proxy;
}
}
// File: contracts/kernel/Kernel.sol
pragma solidity 0.4.24;
// solium-disable-next-line max-len
contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstants, Petrifiable, IsContract, VaultRecoverable, AppProxyFactory, ACLSyntaxSugar {
/* Hardcoded constants to save gas
bytes32 public constant APP_MANAGER_ROLE = keccak256("APP_MANAGER_ROLE");
*/
bytes32 public constant APP_MANAGER_ROLE = 0xb6d92708f3d4817afc106147d969e229ced5c46e65e0a5002a0d391287762bd0;
string private constant ERROR_APP_NOT_CONTRACT = "KERNEL_APP_NOT_CONTRACT";
string private constant ERROR_INVALID_APP_CHANGE = "KERNEL_INVALID_APP_CHANGE";
string private constant ERROR_AUTH_FAILED = "KERNEL_AUTH_FAILED";
/**
* @dev Constructor that allows the deployer to choose if the base instance should be petrified immediately.
* @param _shouldPetrify Immediately petrify this instance so that it can never be initialized
*/
constructor(bool _shouldPetrify) public {
if (_shouldPetrify) {
petrify();
}
}
/**
* @dev Initialize can only be called once. It saves the block number in which it was initialized.
* @notice Initialize this kernel instance along with its ACL and set `_permissionsCreator` as the entity that can create other permissions
* @param _baseAcl Address of base ACL app
* @param _permissionsCreator Entity that will be given permission over createPermission
*/
function initialize(IACL _baseAcl, address _permissionsCreator) public onlyInit {
initialized();
// Set ACL base
_setApp(KERNEL_APP_BASES_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID, _baseAcl);
// Create ACL instance and attach it as the default ACL app
IACL acl = IACL(newAppProxy(this, KERNEL_DEFAULT_ACL_APP_ID));
acl.initialize(_permissionsCreator);
_setApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID, acl);
recoveryVaultAppId = KERNEL_DEFAULT_VAULT_APP_ID;
}
/**
* @dev Create a new instance of an app linked to this kernel
* @notice Create a new upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @return AppProxy instance
*/
function newAppInstance(bytes32 _appId, address _appBase)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
return newAppInstance(_appId, _appBase, new bytes(0), false);
}
/**
* @dev Create a new instance of an app linked to this kernel and set its base
* implementation if it was not already set
* @notice Create a new upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`. `_setDefault ? 'Also sets it as the default app instance.':''`
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @param _initializePayload Payload for call made by the proxy during its construction to initialize
* @param _setDefault Whether the app proxy app is the default one.
* Useful when the Kernel needs to know of an instance of a particular app,
* like Vault for escape hatch mechanism.
* @return AppProxy instance
*/
function newAppInstance(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
_setAppIfNew(KERNEL_APP_BASES_NAMESPACE, _appId, _appBase);
appProxy = newAppProxy(this, _appId, _initializePayload);
// By calling setApp directly and not the internal functions, we make sure the params are checked
// and it will only succeed if sender has permissions to set something to the namespace.
if (_setDefault) {
setApp(KERNEL_APP_ADDR_NAMESPACE, _appId, appProxy);
}
}
/**
* @dev Create a new pinned instance of an app linked to this kernel
* @notice Create a new non-upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`.
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @return AppProxy instance
*/
function newPinnedAppInstance(bytes32 _appId, address _appBase)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
return newPinnedAppInstance(_appId, _appBase, new bytes(0), false);
}
/**
* @dev Create a new pinned instance of an app linked to this kernel and set
* its base implementation if it was not already set
* @notice Create a new non-upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`. `_setDefault ? 'Also sets it as the default app instance.':''`
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @param _initializePayload Payload for call made by the proxy during its construction to initialize
* @param _setDefault Whether the app proxy app is the default one.
* Useful when the Kernel needs to know of an instance of a particular app,
* like Vault for escape hatch mechanism.
* @return AppProxy instance
*/
function newPinnedAppInstance(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
_setAppIfNew(KERNEL_APP_BASES_NAMESPACE, _appId, _appBase);
appProxy = newAppProxyPinned(this, _appId, _initializePayload);
// By calling setApp directly and not the internal functions, we make sure the params are checked
// and it will only succeed if sender has permissions to set something to the namespace.
if (_setDefault) {
setApp(KERNEL_APP_ADDR_NAMESPACE, _appId, appProxy);
}
}
/**
* @dev Set the resolving address of an app instance or base implementation
* @notice Set the resolving address of `_appId` in namespace `_namespace` to `_app`
* @param _namespace App namespace to use
* @param _appId Identifier for app
* @param _app Address of the app instance or base implementation
* @return ID of app
*/
function setApp(bytes32 _namespace, bytes32 _appId, address _app)
public
auth(APP_MANAGER_ROLE, arr(_namespace, _appId))
{
_setApp(_namespace, _appId, _app);
}
/**
* @dev Set the default vault id for the escape hatch mechanism
* @param _recoveryVaultAppId Identifier of the recovery vault app
*/
function setRecoveryVaultAppId(bytes32 _recoveryVaultAppId)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_ADDR_NAMESPACE, _recoveryVaultAppId))
{
recoveryVaultAppId = _recoveryVaultAppId;
}
// External access to default app id and namespace constants to mimic default getters for constants
/* solium-disable function-order, mixedcase */
function CORE_NAMESPACE() external pure returns (bytes32) { return KERNEL_CORE_NAMESPACE; }
function APP_BASES_NAMESPACE() external pure returns (bytes32) { return KERNEL_APP_BASES_NAMESPACE; }
function APP_ADDR_NAMESPACE() external pure returns (bytes32) { return KERNEL_APP_ADDR_NAMESPACE; }
function KERNEL_APP_ID() external pure returns (bytes32) { return KERNEL_CORE_APP_ID; }
function DEFAULT_ACL_APP_ID() external pure returns (bytes32) { return KERNEL_DEFAULT_ACL_APP_ID; }
/* solium-enable function-order, mixedcase */
/**
* @dev Get the address of an app instance or base implementation
* @param _namespace App namespace to use
* @param _appId Identifier for app
* @return Address of the app
*/
function getApp(bytes32 _namespace, bytes32 _appId) public view returns (address) {
return apps[_namespace][_appId];
}
/**
* @dev Get the address of the recovery Vault instance (to recover funds)
* @return Address of the Vault
*/
function getRecoveryVault() public view returns (address) {
return apps[KERNEL_APP_ADDR_NAMESPACE][recoveryVaultAppId];
}
/**
* @dev Get the installed ACL app
* @return ACL app
*/
function acl() public view returns (IACL) {
return IACL(getApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID));
}
/**
* @dev Function called by apps to check ACL on kernel or to check permission status
* @param _who Sender of the original call
* @param _where Address of the app
* @param _what Identifier for a group of actions in app
* @param _how Extra data for ACL auth
* @return Boolean indicating whether the ACL allows the role or not.
* Always returns false if the kernel hasn't been initialized yet.
*/
function hasPermission(address _who, address _where, bytes32 _what, bytes _how) public view returns (bool) {
IACL defaultAcl = acl();
return address(defaultAcl) != address(0) && // Poor man's initialization check (saves gas)
defaultAcl.hasPermission(_who, _where, _what, _how);
}
function _setApp(bytes32 _namespace, bytes32 _appId, address _app) internal {
require(isContract(_app), ERROR_APP_NOT_CONTRACT);
apps[_namespace][_appId] = _app;
emit SetApp(_namespace, _appId, _app);
}
function _setAppIfNew(bytes32 _namespace, bytes32 _appId, address _app) internal {
address app = getApp(_namespace, _appId);
if (app != address(0)) {
// The only way to set an app is if it passes the isContract check, so no need to check it again
require(app == _app, ERROR_INVALID_APP_CHANGE);
} else {
_setApp(_namespace, _appId, _app);
}
}
modifier auth(bytes32 _role, uint256[] memory _params) {
require(
hasPermission(msg.sender, address(this), _role, ConversionHelpers.dangerouslyCastUintArrayToBytes(_params)),
ERROR_AUTH_FAILED
);
_;
}
}
// File: contracts/kernel/KernelProxy.sol
pragma solidity 0.4.24;
contract KernelProxy is IKernelEvents, KernelStorage, KernelAppIds, KernelNamespaceConstants, IsContract, DepositableDelegateProxy {
/**
* @dev KernelProxy is a proxy contract to a kernel implementation. The implementation
* can update the reference, which effectively upgrades the contract
* @param _kernelImpl Address of the contract used as implementation for kernel
*/
constructor(IKernel _kernelImpl) public {
require(isContract(address(_kernelImpl)));
apps[KERNEL_CORE_NAMESPACE][KERNEL_CORE_APP_ID] = _kernelImpl;
// Note that emitting this event is important for verifying that a KernelProxy instance
// was never upgraded to a malicious Kernel logic contract over its lifespan.
// This starts the "chain of trust", that can be followed through later SetApp() events
// emitted during kernel upgrades.
emit SetApp(KERNEL_CORE_NAMESPACE, KERNEL_CORE_APP_ID, _kernelImpl);
}
/**
* @dev ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy
*/
function proxyType() public pure returns (uint256 proxyTypeId) {
return UPGRADEABLE;
}
/**
* @dev ERC897, the address the proxy would delegate calls to
*/
function implementation() public view returns (address) {
return apps[KERNEL_CORE_NAMESPACE][KERNEL_CORE_APP_ID];
}
}
// File: contracts/common/Autopetrified.sol
/*
* 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: contracts/common/ReentrancyGuard.sol
/*
* 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: contracts/evmscript/IEVMScriptExecutor.sol
/*
* 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: contracts/evmscript/IEVMScriptRegistry.sol
/*
* 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: contracts/evmscript/EVMScriptRunner.sol
/*
* 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: contracts/apps/AragonApp.sol
/*
* 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: contracts/acl/IACLOracle.sol
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
interface IACLOracle {
function canPerform(address who, address where, bytes32 what, uint256[] how) external view returns (bool);
}
// File: contracts/acl/ACL.sol
pragma solidity 0.4.24;
/* solium-disable function-order */
// Allow public initialize() to be first
contract ACL is IACL, TimeHelpers, AragonApp, ACLHelpers {
/* Hardcoded constants to save gas
bytes32 public constant CREATE_PERMISSIONS_ROLE = keccak256("CREATE_PERMISSIONS_ROLE");
*/
bytes32 public constant CREATE_PERMISSIONS_ROLE = 0x0b719b33c83b8e5d300c521cb8b54ae9bd933996a14bef8c2f4e0285d2d2400a;
enum Op { NONE, EQ, NEQ, GT, LT, GTE, LTE, RET, NOT, AND, OR, XOR, IF_ELSE } // op types
struct Param {
uint8 id;
uint8 op;
uint240 value; // even though value is an uint240 it can store addresses
// in the case of 32 byte hashes losing 2 bytes precision isn't a huge deal
// op and id take less than 1 byte each so it can be kept in 1 sstore
}
uint8 internal constant BLOCK_NUMBER_PARAM_ID = 200;
uint8 internal constant TIMESTAMP_PARAM_ID = 201;
// 202 is unused
uint8 internal constant ORACLE_PARAM_ID = 203;
uint8 internal constant LOGIC_OP_PARAM_ID = 204;
uint8 internal constant PARAM_VALUE_PARAM_ID = 205;
// TODO: Add execution times param type?
/* Hardcoded constant to save gas
bytes32 public constant EMPTY_PARAM_HASH = keccak256(uint256(0));
*/
bytes32 public constant EMPTY_PARAM_HASH = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563;
bytes32 public constant NO_PERMISSION = bytes32(0);
address public constant ANY_ENTITY = address(-1);
address public constant BURN_ENTITY = address(1); // address(0) is already used as "no permission manager"
string private constant ERROR_AUTH_INIT_KERNEL = "ACL_AUTH_INIT_KERNEL";
string private constant ERROR_AUTH_NO_MANAGER = "ACL_AUTH_NO_MANAGER";
string private constant ERROR_EXISTENT_MANAGER = "ACL_EXISTENT_MANAGER";
// Whether someone has a permission
mapping (bytes32 => bytes32) internal permissions; // permissions hash => params hash
mapping (bytes32 => Param[]) internal permissionParams; // params hash => params
// Who is the manager of a permission
mapping (bytes32 => address) internal permissionManager;
event SetPermission(address indexed entity, address indexed app, bytes32 indexed role, bool allowed);
event SetPermissionParams(address indexed entity, address indexed app, bytes32 indexed role, bytes32 paramsHash);
event ChangePermissionManager(address indexed app, bytes32 indexed role, address indexed manager);
modifier onlyPermissionManager(address _app, bytes32 _role) {
require(msg.sender == getPermissionManager(_app, _role), ERROR_AUTH_NO_MANAGER);
_;
}
modifier noPermissionManager(address _app, bytes32 _role) {
// only allow permission creation (or re-creation) when there is no manager
require(getPermissionManager(_app, _role) == address(0), ERROR_EXISTENT_MANAGER);
_;
}
/**
* @dev Initialize can only be called once. It saves the block number in which it was initialized.
* @notice Initialize an ACL instance and set `_permissionsCreator` as the entity that can create other permissions
* @param _permissionsCreator Entity that will be given permission over createPermission
*/
function initialize(address _permissionsCreator) public onlyInit {
initialized();
require(msg.sender == address(kernel()), ERROR_AUTH_INIT_KERNEL);
_createPermission(_permissionsCreator, this, CREATE_PERMISSIONS_ROLE, _permissionsCreator);
}
/**
* @dev Creates a permission that wasn't previously set and managed.
* If a created permission is removed it is possible to reset it with createPermission.
* This is the **ONLY** way to create permissions and set managers to permissions that don't
* have a manager.
* In terms of the ACL being initialized, this function implicitly protects all the other
* state-changing external functions, as they all require the sender to be a manager.
* @notice Create a new permission granting `_entity` the ability to perform actions requiring `_role` on `_app`, setting `_manager` as the permission's manager
* @param _entity Address of the whitelisted entity that will be able to perform the role
* @param _app Address of the app in which the role will be allowed (requires app to depend on kernel for ACL)
* @param _role Identifier for the group of actions in app given access to perform
* @param _manager Address of the entity that will be able to grant and revoke the permission further.
*/
function createPermission(address _entity, address _app, bytes32 _role, address _manager)
external
auth(CREATE_PERMISSIONS_ROLE)
noPermissionManager(_app, _role)
{
_createPermission(_entity, _app, _role, _manager);
}
/**
* @dev Grants permission if allowed. This requires `msg.sender` to be the permission manager
* @notice Grant `_entity` the ability to perform actions requiring `_role` on `_app`
* @param _entity Address of the whitelisted entity that will be able to perform the role
* @param _app Address of the app in which the role will be allowed (requires app to depend on kernel for ACL)
* @param _role Identifier for the group of actions in app given access to perform
*/
function grantPermission(address _entity, address _app, bytes32 _role)
external
{
grantPermissionP(_entity, _app, _role, new uint256[](0));
}
/**
* @dev Grants a permission with parameters if allowed. This requires `msg.sender` to be the permission manager
* @notice Grant `_entity` the ability to perform actions requiring `_role` on `_app`
* @param _entity Address of the whitelisted entity that will be able to perform the role
* @param _app Address of the app in which the role will be allowed (requires app to depend on kernel for ACL)
* @param _role Identifier for the group of actions in app given access to perform
* @param _params Permission parameters
*/
function grantPermissionP(address _entity, address _app, bytes32 _role, uint256[] _params)
public
onlyPermissionManager(_app, _role)
{
bytes32 paramsHash = _params.length > 0 ? _saveParams(_params) : EMPTY_PARAM_HASH;
_setPermission(_entity, _app, _role, paramsHash);
}
/**
* @dev Revokes permission if allowed. This requires `msg.sender` to be the the permission manager
* @notice Revoke from `_entity` the ability to perform actions requiring `_role` on `_app`
* @param _entity Address of the whitelisted entity to revoke access from
* @param _app Address of the app in which the role will be revoked
* @param _role Identifier for the group of actions in app being revoked
*/
function revokePermission(address _entity, address _app, bytes32 _role)
external
onlyPermissionManager(_app, _role)
{
_setPermission(_entity, _app, _role, NO_PERMISSION);
}
/**
* @notice Set `_newManager` as the manager of `_role` in `_app`
* @param _newManager Address for the new manager
* @param _app Address of the app in which the permission management is being transferred
* @param _role Identifier for the group of actions being transferred
*/
function setPermissionManager(address _newManager, address _app, bytes32 _role)
external
onlyPermissionManager(_app, _role)
{
_setPermissionManager(_newManager, _app, _role);
}
/**
* @notice Remove the manager of `_role` in `_app`
* @param _app Address of the app in which the permission is being unmanaged
* @param _role Identifier for the group of actions being unmanaged
*/
function removePermissionManager(address _app, bytes32 _role)
external
onlyPermissionManager(_app, _role)
{
_setPermissionManager(address(0), _app, _role);
}
/**
* @notice Burn non-existent `_role` in `_app`, so no modification can be made to it (grant, revoke, permission manager)
* @param _app Address of the app in which the permission is being burned
* @param _role Identifier for the group of actions being burned
*/
function createBurnedPermission(address _app, bytes32 _role)
external
auth(CREATE_PERMISSIONS_ROLE)
noPermissionManager(_app, _role)
{
_setPermissionManager(BURN_ENTITY, _app, _role);
}
/**
* @notice Burn `_role` in `_app`, so no modification can be made to it (grant, revoke, permission manager)
* @param _app Address of the app in which the permission is being burned
* @param _role Identifier for the group of actions being burned
*/
function burnPermissionManager(address _app, bytes32 _role)
external
onlyPermissionManager(_app, _role)
{
_setPermissionManager(BURN_ENTITY, _app, _role);
}
/**
* @notice Get parameters for permission array length
* @param _entity Address of the whitelisted entity that will be able to perform the role
* @param _app Address of the app
* @param _role Identifier for a group of actions in app
* @return Length of the array
*/
function getPermissionParamsLength(address _entity, address _app, bytes32 _role) external view returns (uint) {
return permissionParams[permissions[permissionHash(_entity, _app, _role)]].length;
}
/**
* @notice Get parameter for permission
* @param _entity Address of the whitelisted entity that will be able to perform the role
* @param _app Address of the app
* @param _role Identifier for a group of actions in app
* @param _index Index of parameter in the array
* @return Parameter (id, op, value)
*/
function getPermissionParam(address _entity, address _app, bytes32 _role, uint _index)
external
view
returns (uint8, uint8, uint240)
{
Param storage param = permissionParams[permissions[permissionHash(_entity, _app, _role)]][_index];
return (param.id, param.op, param.value);
}
/**
* @dev Get manager for permission
* @param _app Address of the app
* @param _role Identifier for a group of actions in app
* @return address of the manager for the permission
*/
function getPermissionManager(address _app, bytes32 _role) public view returns (address) {
return permissionManager[roleHash(_app, _role)];
}
/**
* @dev Function called by apps to check ACL on kernel or to check permission statu
* @param _who Sender of the original call
* @param _where Address of the app
* @param _where Identifier for a group of actions in app
* @param _how Permission parameters
* @return boolean indicating whether the ACL allows the role or not
*/
function hasPermission(address _who, address _where, bytes32 _what, bytes memory _how) public view returns (bool) {
return hasPermission(_who, _where, _what, ConversionHelpers.dangerouslyCastBytesToUintArray(_how));
}
function hasPermission(address _who, address _where, bytes32 _what, uint256[] memory _how) public view returns (bool) {
bytes32 whoParams = permissions[permissionHash(_who, _where, _what)];
if (whoParams != NO_PERMISSION && evalParams(whoParams, _who, _where, _what, _how)) {
return true;
}
bytes32 anyParams = permissions[permissionHash(ANY_ENTITY, _where, _what)];
if (anyParams != NO_PERMISSION && evalParams(anyParams, ANY_ENTITY, _where, _what, _how)) {
return true;
}
return false;
}
function hasPermission(address _who, address _where, bytes32 _what) public view returns (bool) {
uint256[] memory empty = new uint256[](0);
return hasPermission(_who, _where, _what, empty);
}
function evalParams(
bytes32 _paramsHash,
address _who,
address _where,
bytes32 _what,
uint256[] _how
) public view returns (bool)
{
if (_paramsHash == EMPTY_PARAM_HASH) {
return true;
}
return _evalParam(_paramsHash, 0, _who, _where, _what, _how);
}
/**
* @dev Internal createPermission for access inside the kernel (on instantiation)
*/
function _createPermission(address _entity, address _app, bytes32 _role, address _manager) internal {
_setPermission(_entity, _app, _role, EMPTY_PARAM_HASH);
_setPermissionManager(_manager, _app, _role);
}
/**
* @dev Internal function called to actually save the permission
*/
function _setPermission(address _entity, address _app, bytes32 _role, bytes32 _paramsHash) internal {
permissions[permissionHash(_entity, _app, _role)] = _paramsHash;
bool entityHasPermission = _paramsHash != NO_PERMISSION;
bool permissionHasParams = entityHasPermission && _paramsHash != EMPTY_PARAM_HASH;
emit SetPermission(_entity, _app, _role, entityHasPermission);
if (permissionHasParams) {
emit SetPermissionParams(_entity, _app, _role, _paramsHash);
}
}
function _saveParams(uint256[] _encodedParams) internal returns (bytes32) {
bytes32 paramHash = keccak256(abi.encodePacked(_encodedParams));
Param[] storage params = permissionParams[paramHash];
if (params.length == 0) { // params not saved before
for (uint256 i = 0; i < _encodedParams.length; i++) {
uint256 encodedParam = _encodedParams[i];
Param memory param = Param(decodeParamId(encodedParam), decodeParamOp(encodedParam), uint240(encodedParam));
params.push(param);
}
}
return paramHash;
}
function _evalParam(
bytes32 _paramsHash,
uint32 _paramId,
address _who,
address _where,
bytes32 _what,
uint256[] _how
) internal view returns (bool)
{
if (_paramId >= permissionParams[_paramsHash].length) {
return false; // out of bounds
}
Param memory param = permissionParams[_paramsHash][_paramId];
if (param.id == LOGIC_OP_PARAM_ID) {
return _evalLogic(param, _paramsHash, _who, _where, _what, _how);
}
uint256 value;
uint256 comparedTo = uint256(param.value);
// get value
if (param.id == ORACLE_PARAM_ID) {
value = checkOracle(IACLOracle(param.value), _who, _where, _what, _how) ? 1 : 0;
comparedTo = 1;
} else if (param.id == BLOCK_NUMBER_PARAM_ID) {
value = getBlockNumber();
} else if (param.id == TIMESTAMP_PARAM_ID) {
value = getTimestamp();
} else if (param.id == PARAM_VALUE_PARAM_ID) {
value = uint256(param.value);
} else {
if (param.id >= _how.length) {
return false;
}
value = uint256(uint240(_how[param.id])); // force lost precision
}
if (Op(param.op) == Op.RET) {
return uint256(value) > 0;
}
return compare(value, Op(param.op), comparedTo);
}
function _evalLogic(Param _param, bytes32 _paramsHash, address _who, address _where, bytes32 _what, uint256[] _how)
internal
view
returns (bool)
{
if (Op(_param.op) == Op.IF_ELSE) {
uint32 conditionParam;
uint32 successParam;
uint32 failureParam;
(conditionParam, successParam, failureParam) = decodeParamsList(uint256(_param.value));
bool result = _evalParam(_paramsHash, conditionParam, _who, _where, _what, _how);
return _evalParam(_paramsHash, result ? successParam : failureParam, _who, _where, _what, _how);
}
uint32 param1;
uint32 param2;
(param1, param2,) = decodeParamsList(uint256(_param.value));
bool r1 = _evalParam(_paramsHash, param1, _who, _where, _what, _how);
if (Op(_param.op) == Op.NOT) {
return !r1;
}
if (r1 && Op(_param.op) == Op.OR) {
return true;
}
if (!r1 && Op(_param.op) == Op.AND) {
return false;
}
bool r2 = _evalParam(_paramsHash, param2, _who, _where, _what, _how);
if (Op(_param.op) == Op.XOR) {
return r1 != r2;
}
return r2; // both or and and depend on result of r2 after checks
}
function compare(uint256 _a, Op _op, uint256 _b) internal pure returns (bool) {
if (_op == Op.EQ) return _a == _b; // solium-disable-line lbrace
if (_op == Op.NEQ) return _a != _b; // solium-disable-line lbrace
if (_op == Op.GT) return _a > _b; // solium-disable-line lbrace
if (_op == Op.LT) return _a < _b; // solium-disable-line lbrace
if (_op == Op.GTE) return _a >= _b; // solium-disable-line lbrace
if (_op == Op.LTE) return _a <= _b; // solium-disable-line lbrace
return false;
}
function checkOracle(IACLOracle _oracleAddr, address _who, address _where, bytes32 _what, uint256[] _how) internal view returns (bool) {
bytes4 sig = _oracleAddr.canPerform.selector;
// a raw call is required so we can return false if the call reverts, rather than reverting
bytes memory checkCalldata = abi.encodeWithSelector(sig, _who, _where, _what, _how);
bool ok;
assembly {
// send all available gas; if the oracle eats up all the gas, we will eventually revert
// note that we are currently guaranteed to still have some gas after the call from
// EIP-150's 63/64 gas forward rule
ok := staticcall(gas, _oracleAddr, add(checkCalldata, 0x20), mload(checkCalldata), 0, 0)
}
if (!ok) {
return false;
}
uint256 size;
assembly { size := returndatasize }
if (size != 32) {
return false;
}
bool result;
assembly {
let ptr := mload(0x40) // get next free memory ptr
returndatacopy(ptr, 0, size) // copy return from above `staticcall`
result := mload(ptr) // read data at ptr and set it to result
mstore(ptr, 0) // set pointer memory to 0 so it still is the next free ptr
}
return result;
}
/**
* @dev Internal function that sets management
*/
function _setPermissionManager(address _newManager, address _app, bytes32 _role) internal {
permissionManager[roleHash(_app, _role)] = _newManager;
emit ChangePermissionManager(_app, _role, _newManager);
}
function roleHash(address _where, bytes32 _what) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("ROLE", _where, _what));
}
function permissionHash(address _who, address _where, bytes32 _what) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("PERMISSION", _who, _where, _what));
}
}
// File: contracts/evmscript/ScriptHelpers.sol
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
library ScriptHelpers {
function getSpecId(bytes _script) internal pure returns (uint32) {
return uint32At(_script, 0);
}
function uint256At(bytes _data, uint256 _location) internal pure returns (uint256 result) {
assembly {
result := mload(add(_data, add(0x20, _location)))
}
}
function addressAt(bytes _data, uint256 _location) internal pure returns (address result) {
uint256 word = uint256At(_data, _location);
assembly {
result := div(and(word, 0xffffffffffffffffffffffffffffffffffffffff000000000000000000000000),
0x1000000000000000000000000)
}
}
function uint32At(bytes _data, uint256 _location) internal pure returns (uint32 result) {
uint256 word = uint256At(_data, _location);
assembly {
result := div(and(word, 0xffffffff00000000000000000000000000000000000000000000000000000000),
0x100000000000000000000000000000000000000000000000000000000)
}
}
function locationOf(bytes _data, uint256 _location) internal pure returns (uint256 result) {
assembly {
result := add(_data, add(0x20, _location))
}
}
function toBytes(bytes4 _sig) internal pure returns (bytes) {
bytes memory payload = new bytes(4);
assembly { mstore(add(payload, 0x20), _sig) }
return payload;
}
}
// File: contracts/evmscript/EVMScriptRegistry.sol
pragma solidity 0.4.24;
/* solium-disable function-order */
// Allow public initialize() to be first
contract EVMScriptRegistry is IEVMScriptRegistry, EVMScriptRegistryConstants, AragonApp {
using ScriptHelpers for bytes;
/* Hardcoded constants to save gas
bytes32 public constant REGISTRY_ADD_EXECUTOR_ROLE = keccak256("REGISTRY_ADD_EXECUTOR_ROLE");
bytes32 public constant REGISTRY_MANAGER_ROLE = keccak256("REGISTRY_MANAGER_ROLE");
*/
bytes32 public constant REGISTRY_ADD_EXECUTOR_ROLE = 0xc4e90f38eea8c4212a009ca7b8947943ba4d4a58d19b683417f65291d1cd9ed2;
// WARN: Manager can censor all votes and the like happening in an org
bytes32 public constant REGISTRY_MANAGER_ROLE = 0xf7a450ef335e1892cb42c8ca72e7242359d7711924b75db5717410da3f614aa3;
uint256 internal constant SCRIPT_START_LOCATION = 4;
string private constant ERROR_INEXISTENT_EXECUTOR = "EVMREG_INEXISTENT_EXECUTOR";
string private constant ERROR_EXECUTOR_ENABLED = "EVMREG_EXECUTOR_ENABLED";
string private constant ERROR_EXECUTOR_DISABLED = "EVMREG_EXECUTOR_DISABLED";
string private constant ERROR_SCRIPT_LENGTH_TOO_SHORT = "EVMREG_SCRIPT_LENGTH_TOO_SHORT";
struct ExecutorEntry {
IEVMScriptExecutor executor;
bool enabled;
}
uint256 private executorsNextIndex;
mapping (uint256 => ExecutorEntry) public executors;
event EnableExecutor(uint256 indexed executorId, address indexed executorAddress);
event DisableExecutor(uint256 indexed executorId, address indexed executorAddress);
modifier executorExists(uint256 _executorId) {
require(_executorId > 0 && _executorId < executorsNextIndex, ERROR_INEXISTENT_EXECUTOR);
_;
}
/**
* @notice Initialize the registry
*/
function initialize() public onlyInit {
initialized();
// Create empty record to begin executor IDs at 1
executorsNextIndex = 1;
}
/**
* @notice Add a new script executor with address `_executor` to the registry
* @param _executor Address of the IEVMScriptExecutor that will be added to the registry
* @return id Identifier of the executor in the registry
*/
function addScriptExecutor(IEVMScriptExecutor _executor) external auth(REGISTRY_ADD_EXECUTOR_ROLE) returns (uint256 id) {
uint256 executorId = executorsNextIndex++;
executors[executorId] = ExecutorEntry(_executor, true);
emit EnableExecutor(executorId, _executor);
return executorId;
}
/**
* @notice Disable script executor with ID `_executorId`
* @param _executorId Identifier of the executor in the registry
*/
function disableScriptExecutor(uint256 _executorId)
external
authP(REGISTRY_MANAGER_ROLE, arr(_executorId))
{
// Note that we don't need to check for an executor's existence in this case, as only
// existing executors can be enabled
ExecutorEntry storage executorEntry = executors[_executorId];
require(executorEntry.enabled, ERROR_EXECUTOR_DISABLED);
executorEntry.enabled = false;
emit DisableExecutor(_executorId, executorEntry.executor);
}
/**
* @notice Enable script executor with ID `_executorId`
* @param _executorId Identifier of the executor in the registry
*/
function enableScriptExecutor(uint256 _executorId)
external
authP(REGISTRY_MANAGER_ROLE, arr(_executorId))
executorExists(_executorId)
{
ExecutorEntry storage executorEntry = executors[_executorId];
require(!executorEntry.enabled, ERROR_EXECUTOR_ENABLED);
executorEntry.enabled = true;
emit EnableExecutor(_executorId, executorEntry.executor);
}
/**
* @dev Get the script executor that can execute a particular script based on its first 4 bytes
* @param _script EVMScript being inspected
*/
function getScriptExecutor(bytes _script) public view returns (IEVMScriptExecutor) {
require(_script.length >= SCRIPT_START_LOCATION, ERROR_SCRIPT_LENGTH_TOO_SHORT);
uint256 id = _script.getSpecId();
// Note that we don't need to check for an executor's existence in this case, as only
// existing executors can be enabled
ExecutorEntry storage entry = executors[id];
return entry.enabled ? entry.executor : IEVMScriptExecutor(0);
}
}
// File: contracts/evmscript/executors/BaseEVMScriptExecutor.sol
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
contract BaseEVMScriptExecutor is IEVMScriptExecutor, Autopetrified {
uint256 internal constant SCRIPT_START_LOCATION = 4;
}
// File: contracts/evmscript/executors/CallsScript.sol
pragma solidity 0.4.24;
// Inspired by https://github.com/reverendus/tx-manager
contract CallsScript is BaseEVMScriptExecutor {
using ScriptHelpers for bytes;
/* Hardcoded constants to save gas
bytes32 internal constant EXECUTOR_TYPE = keccak256("CALLS_SCRIPT");
*/
bytes32 internal constant EXECUTOR_TYPE = 0x2dc858a00f3e417be1394b87c07158e989ec681ce8cc68a9093680ac1a870302;
string private constant ERROR_BLACKLISTED_CALL = "EVMCALLS_BLACKLISTED_CALL";
string private constant ERROR_INVALID_LENGTH = "EVMCALLS_INVALID_LENGTH";
/* This is manually crafted in assembly
string private constant ERROR_CALL_REVERTED = "EVMCALLS_CALL_REVERTED";
*/
event LogScriptCall(address indexed sender, address indexed src, address indexed dst);
/**
* @notice Executes a number of call scripts
* @param _script [ specId (uint32) ] many calls with this structure ->
* [ to (address: 20 bytes) ] [ calldataLength (uint32: 4 bytes) ] [ calldata (calldataLength bytes) ]
* @param _blacklist Addresses the script cannot call to, or will revert.
* @return Always returns empty byte array
*/
function execScript(bytes _script, bytes, address[] _blacklist) external isInitialized returns (bytes) {
uint256 location = SCRIPT_START_LOCATION; // first 32 bits are spec id
while (location < _script.length) {
// Check there's at least address + calldataLength available
require(_script.length - location >= 0x18, ERROR_INVALID_LENGTH);
address contractAddress = _script.addressAt(location);
// Check address being called is not blacklist
for (uint256 i = 0; i < _blacklist.length; i++) {
require(contractAddress != _blacklist[i], ERROR_BLACKLISTED_CALL);
}
// logged before execution to ensure event ordering in receipt
// if failed entire execution is reverted regardless
emit LogScriptCall(msg.sender, address(this), contractAddress);
uint256 calldataLength = uint256(_script.uint32At(location + 0x14));
uint256 startOffset = location + 0x14 + 0x04;
uint256 calldataStart = _script.locationOf(startOffset);
// compute end of script / next location
location = startOffset + calldataLength;
require(location <= _script.length, ERROR_INVALID_LENGTH);
bool success;
assembly {
success := call(
sub(gas, 5000), // forward gas left - 5000
contractAddress, // address
0, // no value
calldataStart, // calldata start
calldataLength, // calldata length
0, // don't write output
0 // don't write output
)
switch success
case 0 {
let ptr := mload(0x40)
switch returndatasize
case 0 {
// No error data was returned, revert with "EVMCALLS_CALL_REVERTED"
// See remix: doing a `revert("EVMCALLS_CALL_REVERTED")` always results in
// this memory layout
mstore(ptr, 0x08c379a000000000000000000000000000000000000000000000000000000000) // error identifier
mstore(add(ptr, 0x04), 0x0000000000000000000000000000000000000000000000000000000000000020) // starting offset
mstore(add(ptr, 0x24), 0x0000000000000000000000000000000000000000000000000000000000000016) // reason length
mstore(add(ptr, 0x44), 0x45564d43414c4c535f43414c4c5f524556455254454400000000000000000000) // reason
revert(ptr, 100) // 100 = 4 + 3 * 32 (error identifier + 3 words for the ABI encoded error)
}
default {
// Forward the full error data
returndatacopy(ptr, 0, returndatasize)
revert(ptr, returndatasize)
}
}
default { }
}
}
// No need to allocate empty bytes for the return as this can only be called via an delegatecall
// (due to the isInitialized modifier)
}
function executorType() external pure returns (bytes32) {
return EXECUTOR_TYPE;
}
}
// File: contracts/factory/EVMScriptRegistryFactory.sol
pragma solidity 0.4.24;
contract EVMScriptRegistryFactory is EVMScriptRegistryConstants {
EVMScriptRegistry public baseReg;
IEVMScriptExecutor public baseCallScript;
/**
* @notice Create a new EVMScriptRegistryFactory.
*/
constructor() public {
baseReg = new EVMScriptRegistry();
baseCallScript = IEVMScriptExecutor(new CallsScript());
}
/**
* @notice Install a new pinned instance of EVMScriptRegistry on `_dao`.
* @param _dao Kernel
* @return Installed EVMScriptRegistry
*/
function newEVMScriptRegistry(Kernel _dao) public returns (EVMScriptRegistry reg) {
bytes memory initPayload = abi.encodeWithSelector(reg.initialize.selector);
reg = EVMScriptRegistry(_dao.newPinnedAppInstance(EVMSCRIPT_REGISTRY_APP_ID, baseReg, initPayload, true));
ACL acl = ACL(_dao.acl());
acl.createPermission(this, reg, reg.REGISTRY_ADD_EXECUTOR_ROLE(), this);
reg.addScriptExecutor(baseCallScript); // spec 1 = CallsScript
// Clean up the permissions
acl.revokePermission(this, reg, reg.REGISTRY_ADD_EXECUTOR_ROLE());
acl.removePermissionManager(reg, reg.REGISTRY_ADD_EXECUTOR_ROLE());
return reg;
}
}
// File: contracts/factory/DAOFactory.sol
pragma solidity 0.4.24;
contract DAOFactory {
IKernel public baseKernel;
IACL public baseACL;
EVMScriptRegistryFactory public regFactory;
event DeployDAO(address dao);
event DeployEVMScriptRegistry(address reg);
/**
* @notice Create a new DAOFactory, creating DAOs with Kernels proxied to `_baseKernel`, ACLs proxied to `_baseACL`, and new EVMScriptRegistries created from `_regFactory`.
* @param _baseKernel Base Kernel
* @param _baseACL Base ACL
* @param _regFactory EVMScriptRegistry factory
*/
constructor(IKernel _baseKernel, IACL _baseACL, EVMScriptRegistryFactory _regFactory) public {
// No need to init as it cannot be killed by devops199
if (address(_regFactory) != address(0)) {
regFactory = _regFactory;
}
baseKernel = _baseKernel;
baseACL = _baseACL;
}
/**
* @notice Create a new DAO with `_root` set as the initial admin
* @param _root Address that will be granted control to setup DAO permissions
* @return Newly created DAO
*/
function newDAO(address _root) public returns (Kernel) {
Kernel dao = Kernel(new KernelProxy(baseKernel));
if (address(regFactory) == address(0)) {
dao.initialize(baseACL, _root);
} else {
dao.initialize(baseACL, this);
ACL acl = ACL(dao.acl());
bytes32 permRole = acl.CREATE_PERMISSIONS_ROLE();
bytes32 appManagerRole = dao.APP_MANAGER_ROLE();
acl.grantPermission(regFactory, acl, permRole);
acl.createPermission(regFactory, dao, appManagerRole, this);
EVMScriptRegistry reg = regFactory.newEVMScriptRegistry(dao);
emit DeployEVMScriptRegistry(address(reg));
// Clean up permissions
// First, completely reset the APP_MANAGER_ROLE
acl.revokePermission(regFactory, dao, appManagerRole);
acl.removePermissionManager(dao, appManagerRole);
// Then, make root the only holder and manager of CREATE_PERMISSIONS_ROLE
acl.revokePermission(regFactory, acl, permRole);
acl.revokePermission(this, acl, permRole);
acl.grantPermission(_root, acl, permRole);
acl.setPermissionManager(_root, acl, permRole);
}
emit DeployDAO(address(dao));
return dao;
}
}File 3 of 4: Kernel
// File: contracts/acl/IACL.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/IVaultRecoverable.sol
/*
* SPDX-License-Identitifer: 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: contracts/kernel/IKernel.sol
/*
* SPDX-License-Identitifer: 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: contracts/kernel/KernelConstants.sol
/*
* SPDX-License-Identitifer: 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: contracts/kernel/KernelStorage.sol
pragma solidity 0.4.24;
contract KernelStorage {
// namespace => app id => address
mapping (bytes32 => mapping (bytes32 => address)) public apps;
bytes32 public recoveryVaultAppId;
}
// File: contracts/acl/ACLSyntaxSugar.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/ConversionHelpers.sol
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: contracts/common/IsContract.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/Uint256Helpers.sol
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: contracts/common/TimeHelpers.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/UnstructuredStorage.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/Initializable.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/Petrifiable.sol
/*
* SPDX-License-Identitifer: 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: contracts/lib/token/ERC20.sol
// 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: contracts/common/EtherTokenConstant.sol
/*
* SPDX-License-Identitifer: 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: contracts/common/SafeERC20.sol
// 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: contracts/common/VaultRecoverable.sol
/*
* SPDX-License-Identitifer: 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: contracts/apps/AppStorage.sol
/*
* SPDX-License-Identitifer: 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: contracts/lib/misc/ERCProxy.sol
/*
* SPDX-License-Identitifer: MIT
*/
pragma solidity ^0.4.24;
contract ERCProxy {
uint256 internal constant FORWARDING = 1;
uint256 internal constant UPGRADEABLE = 2;
function proxyType() public pure returns (uint256 proxyTypeId);
function implementation() public view returns (address codeAddr);
}
// File: contracts/common/DelegateProxy.sol
pragma solidity 0.4.24;
contract DelegateProxy is ERCProxy, IsContract {
uint256 internal constant FWD_GAS_LIMIT = 10000;
/**
* @dev Performs a delegatecall and returns whatever the delegatecall returned (entire context execution will return!)
* @param _dst Destination address to perform the delegatecall
* @param _calldata Calldata for the delegatecall
*/
function delegatedFwd(address _dst, bytes _calldata) internal {
require(isContract(_dst));
uint256 fwdGasLimit = FWD_GAS_LIMIT;
assembly {
let result := delegatecall(sub(gas, fwdGasLimit), _dst, add(_calldata, 0x20), mload(_calldata), 0, 0)
let size := returndatasize
let ptr := mload(0x40)
returndatacopy(ptr, 0, size)
// revert instead of invalid() bc if the underlying call failed with invalid() it already wasted gas.
// if the call returned error data, forward it
switch result case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
// File: contracts/common/DepositableStorage.sol
pragma solidity 0.4.24;
contract DepositableStorage {
using UnstructuredStorage for bytes32;
// keccak256("aragonOS.depositableStorage.depositable")
bytes32 internal constant DEPOSITABLE_POSITION = 0x665fd576fbbe6f247aff98f5c94a561e3f71ec2d3c988d56f12d342396c50cea;
function isDepositable() public view returns (bool) {
return DEPOSITABLE_POSITION.getStorageBool();
}
function setDepositable(bool _depositable) internal {
DEPOSITABLE_POSITION.setStorageBool(_depositable);
}
}
// File: contracts/common/DepositableDelegateProxy.sol
pragma solidity 0.4.24;
contract DepositableDelegateProxy is DepositableStorage, DelegateProxy {
event ProxyDeposit(address sender, uint256 value);
function () external payable {
uint256 forwardGasThreshold = FWD_GAS_LIMIT;
bytes32 isDepositablePosition = DEPOSITABLE_POSITION;
// Optimized assembly implementation to prevent EIP-1884 from breaking deposits, reference code in Solidity:
// https://github.com/aragon/aragonOS/blob/v4.2.1/contracts/common/DepositableDelegateProxy.sol#L10-L20
assembly {
// Continue only if the gas left is lower than the threshold for forwarding to the implementation code,
// otherwise continue outside of the assembly block.
if lt(gas, forwardGasThreshold) {
// Only accept the deposit and emit an event if all of the following are true:
// the proxy accepts deposits (isDepositable), msg.data.length == 0, and msg.value > 0
if and(and(sload(isDepositablePosition), iszero(calldatasize)), gt(callvalue, 0)) {
// Equivalent Solidity code for emitting the event:
// emit ProxyDeposit(msg.sender, msg.value);
let logData := mload(0x40) // free memory pointer
mstore(logData, caller) // add 'msg.sender' to the log data (first event param)
mstore(add(logData, 0x20), callvalue) // add 'msg.value' to the log data (second event param)
// Emit an event with one topic to identify the event: keccak256('ProxyDeposit(address,uint256)') = 0x15ee...dee1
log1(logData, 0x40, 0x15eeaa57c7bd188c1388020bcadc2c436ec60d647d36ef5b9eb3c742217ddee1)
stop() // Stop. Exits execution context
}
// If any of above checks failed, revert the execution (if ETH was sent, it is returned to the sender)
revert(0, 0)
}
}
address target = implementation();
delegatedFwd(target, msg.data);
}
}
// File: contracts/apps/AppProxyBase.sol
pragma solidity 0.4.24;
contract AppProxyBase is AppStorage, DepositableDelegateProxy, KernelNamespaceConstants {
/**
* @dev Initialize AppProxy
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload) public {
setKernel(_kernel);
setAppId(_appId);
// Implicit check that kernel is actually a Kernel
// The EVM doesn't actually provide a way for us to make sure, but we can force a revert to
// occur if the kernel is set to 0x0 or a non-code address when we try to call a method on
// it.
address appCode = getAppBase(_appId);
// If initialize payload is provided, it will be executed
if (_initializePayload.length > 0) {
require(isContract(appCode));
// Cannot make delegatecall as a delegateproxy.delegatedFwd as it
// returns ending execution context and halts contract deployment
require(appCode.delegatecall(_initializePayload));
}
}
function getAppBase(bytes32 _appId) internal view returns (address) {
return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, _appId);
}
}
// File: contracts/apps/AppProxyUpgradeable.sol
pragma solidity 0.4.24;
contract AppProxyUpgradeable is AppProxyBase {
/**
* @dev Initialize AppProxyUpgradeable (makes it an upgradeable Aragon app)
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload)
AppProxyBase(_kernel, _appId, _initializePayload)
public // solium-disable-line visibility-first
{
// solium-disable-previous-line no-empty-blocks
}
/**
* @dev ERC897, the address the proxy would delegate calls to
*/
function implementation() public view returns (address) {
return getAppBase(appId());
}
/**
* @dev ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy
*/
function proxyType() public pure returns (uint256 proxyTypeId) {
return UPGRADEABLE;
}
}
// File: contracts/apps/AppProxyPinned.sol
pragma solidity 0.4.24;
contract AppProxyPinned is IsContract, AppProxyBase {
using UnstructuredStorage for bytes32;
// keccak256("aragonOS.appStorage.pinnedCode")
bytes32 internal constant PINNED_CODE_POSITION = 0xdee64df20d65e53d7f51cb6ab6d921a0a6a638a91e942e1d8d02df28e31c038e;
/**
* @dev Initialize AppProxyPinned (makes it an un-upgradeable Aragon app)
* @param _kernel Reference to organization kernel for the app
* @param _appId Identifier for app
* @param _initializePayload Payload for call to be made after setup to initialize
*/
constructor(IKernel _kernel, bytes32 _appId, bytes _initializePayload)
AppProxyBase(_kernel, _appId, _initializePayload)
public // solium-disable-line visibility-first
{
setPinnedCode(getAppBase(_appId));
require(isContract(pinnedCode()));
}
/**
* @dev ERC897, the address the proxy would delegate calls to
*/
function implementation() public view returns (address) {
return pinnedCode();
}
/**
* @dev ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy
*/
function proxyType() public pure returns (uint256 proxyTypeId) {
return FORWARDING;
}
function setPinnedCode(address _pinnedCode) internal {
PINNED_CODE_POSITION.setStorageAddress(_pinnedCode);
}
function pinnedCode() internal view returns (address) {
return PINNED_CODE_POSITION.getStorageAddress();
}
}
// File: contracts/factory/AppProxyFactory.sol
pragma solidity 0.4.24;
contract AppProxyFactory {
event NewAppProxy(address proxy, bool isUpgradeable, bytes32 appId);
/**
* @notice Create a new upgradeable app instance on `_kernel` with identifier `_appId`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @return AppProxyUpgradeable
*/
function newAppProxy(IKernel _kernel, bytes32 _appId) public returns (AppProxyUpgradeable) {
return newAppProxy(_kernel, _appId, new bytes(0));
}
/**
* @notice Create a new upgradeable app instance on `_kernel` with identifier `_appId` and initialization payload `_initializePayload`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @return AppProxyUpgradeable
*/
function newAppProxy(IKernel _kernel, bytes32 _appId, bytes _initializePayload) public returns (AppProxyUpgradeable) {
AppProxyUpgradeable proxy = new AppProxyUpgradeable(_kernel, _appId, _initializePayload);
emit NewAppProxy(address(proxy), true, _appId);
return proxy;
}
/**
* @notice Create a new pinned app instance on `_kernel` with identifier `_appId`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @return AppProxyPinned
*/
function newAppProxyPinned(IKernel _kernel, bytes32 _appId) public returns (AppProxyPinned) {
return newAppProxyPinned(_kernel, _appId, new bytes(0));
}
/**
* @notice Create a new pinned app instance on `_kernel` with identifier `_appId` and initialization payload `_initializePayload`
* @param _kernel App's Kernel reference
* @param _appId Identifier for app
* @param _initializePayload Proxy initialization payload
* @return AppProxyPinned
*/
function newAppProxyPinned(IKernel _kernel, bytes32 _appId, bytes _initializePayload) public returns (AppProxyPinned) {
AppProxyPinned proxy = new AppProxyPinned(_kernel, _appId, _initializePayload);
emit NewAppProxy(address(proxy), false, _appId);
return proxy;
}
}
// File: contracts/kernel/Kernel.sol
pragma solidity 0.4.24;
// solium-disable-next-line max-len
contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstants, Petrifiable, IsContract, VaultRecoverable, AppProxyFactory, ACLSyntaxSugar {
/* Hardcoded constants to save gas
bytes32 public constant APP_MANAGER_ROLE = keccak256("APP_MANAGER_ROLE");
*/
bytes32 public constant APP_MANAGER_ROLE = 0xb6d92708f3d4817afc106147d969e229ced5c46e65e0a5002a0d391287762bd0;
string private constant ERROR_APP_NOT_CONTRACT = "KERNEL_APP_NOT_CONTRACT";
string private constant ERROR_INVALID_APP_CHANGE = "KERNEL_INVALID_APP_CHANGE";
string private constant ERROR_AUTH_FAILED = "KERNEL_AUTH_FAILED";
/**
* @dev Constructor that allows the deployer to choose if the base instance should be petrified immediately.
* @param _shouldPetrify Immediately petrify this instance so that it can never be initialized
*/
constructor(bool _shouldPetrify) public {
if (_shouldPetrify) {
petrify();
}
}
/**
* @dev Initialize can only be called once. It saves the block number in which it was initialized.
* @notice Initialize this kernel instance along with its ACL and set `_permissionsCreator` as the entity that can create other permissions
* @param _baseAcl Address of base ACL app
* @param _permissionsCreator Entity that will be given permission over createPermission
*/
function initialize(IACL _baseAcl, address _permissionsCreator) public onlyInit {
initialized();
// Set ACL base
_setApp(KERNEL_APP_BASES_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID, _baseAcl);
// Create ACL instance and attach it as the default ACL app
IACL acl = IACL(newAppProxy(this, KERNEL_DEFAULT_ACL_APP_ID));
acl.initialize(_permissionsCreator);
_setApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID, acl);
recoveryVaultAppId = KERNEL_DEFAULT_VAULT_APP_ID;
}
/**
* @dev Create a new instance of an app linked to this kernel
* @notice Create a new upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @return AppProxy instance
*/
function newAppInstance(bytes32 _appId, address _appBase)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
return newAppInstance(_appId, _appBase, new bytes(0), false);
}
/**
* @dev Create a new instance of an app linked to this kernel and set its base
* implementation if it was not already set
* @notice Create a new upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`. `_setDefault ? 'Also sets it as the default app instance.':''`
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @param _initializePayload Payload for call made by the proxy during its construction to initialize
* @param _setDefault Whether the app proxy app is the default one.
* Useful when the Kernel needs to know of an instance of a particular app,
* like Vault for escape hatch mechanism.
* @return AppProxy instance
*/
function newAppInstance(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
_setAppIfNew(KERNEL_APP_BASES_NAMESPACE, _appId, _appBase);
appProxy = newAppProxy(this, _appId, _initializePayload);
// By calling setApp directly and not the internal functions, we make sure the params are checked
// and it will only succeed if sender has permissions to set something to the namespace.
if (_setDefault) {
setApp(KERNEL_APP_ADDR_NAMESPACE, _appId, appProxy);
}
}
/**
* @dev Create a new pinned instance of an app linked to this kernel
* @notice Create a new non-upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`.
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @return AppProxy instance
*/
function newPinnedAppInstance(bytes32 _appId, address _appBase)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
return newPinnedAppInstance(_appId, _appBase, new bytes(0), false);
}
/**
* @dev Create a new pinned instance of an app linked to this kernel and set
* its base implementation if it was not already set
* @notice Create a new non-upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase`. `_setDefault ? 'Also sets it as the default app instance.':''`
* @param _appId Identifier for app
* @param _appBase Address of the app's base implementation
* @param _initializePayload Payload for call made by the proxy during its construction to initialize
* @param _setDefault Whether the app proxy app is the default one.
* Useful when the Kernel needs to know of an instance of a particular app,
* like Vault for escape hatch mechanism.
* @return AppProxy instance
*/
function newPinnedAppInstance(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId))
returns (ERCProxy appProxy)
{
_setAppIfNew(KERNEL_APP_BASES_NAMESPACE, _appId, _appBase);
appProxy = newAppProxyPinned(this, _appId, _initializePayload);
// By calling setApp directly and not the internal functions, we make sure the params are checked
// and it will only succeed if sender has permissions to set something to the namespace.
if (_setDefault) {
setApp(KERNEL_APP_ADDR_NAMESPACE, _appId, appProxy);
}
}
/**
* @dev Set the resolving address of an app instance or base implementation
* @notice Set the resolving address of `_appId` in namespace `_namespace` to `_app`
* @param _namespace App namespace to use
* @param _appId Identifier for app
* @param _app Address of the app instance or base implementation
* @return ID of app
*/
function setApp(bytes32 _namespace, bytes32 _appId, address _app)
public
auth(APP_MANAGER_ROLE, arr(_namespace, _appId))
{
_setApp(_namespace, _appId, _app);
}
/**
* @dev Set the default vault id for the escape hatch mechanism
* @param _recoveryVaultAppId Identifier of the recovery vault app
*/
function setRecoveryVaultAppId(bytes32 _recoveryVaultAppId)
public
auth(APP_MANAGER_ROLE, arr(KERNEL_APP_ADDR_NAMESPACE, _recoveryVaultAppId))
{
recoveryVaultAppId = _recoveryVaultAppId;
}
// External access to default app id and namespace constants to mimic default getters for constants
/* solium-disable function-order, mixedcase */
function CORE_NAMESPACE() external pure returns (bytes32) { return KERNEL_CORE_NAMESPACE; }
function APP_BASES_NAMESPACE() external pure returns (bytes32) { return KERNEL_APP_BASES_NAMESPACE; }
function APP_ADDR_NAMESPACE() external pure returns (bytes32) { return KERNEL_APP_ADDR_NAMESPACE; }
function KERNEL_APP_ID() external pure returns (bytes32) { return KERNEL_CORE_APP_ID; }
function DEFAULT_ACL_APP_ID() external pure returns (bytes32) { return KERNEL_DEFAULT_ACL_APP_ID; }
/* solium-enable function-order, mixedcase */
/**
* @dev Get the address of an app instance or base implementation
* @param _namespace App namespace to use
* @param _appId Identifier for app
* @return Address of the app
*/
function getApp(bytes32 _namespace, bytes32 _appId) public view returns (address) {
return apps[_namespace][_appId];
}
/**
* @dev Get the address of the recovery Vault instance (to recover funds)
* @return Address of the Vault
*/
function getRecoveryVault() public view returns (address) {
return apps[KERNEL_APP_ADDR_NAMESPACE][recoveryVaultAppId];
}
/**
* @dev Get the installed ACL app
* @return ACL app
*/
function acl() public view returns (IACL) {
return IACL(getApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID));
}
/**
* @dev Function called by apps to check ACL on kernel or to check permission status
* @param _who Sender of the original call
* @param _where Address of the app
* @param _what Identifier for a group of actions in app
* @param _how Extra data for ACL auth
* @return Boolean indicating whether the ACL allows the role or not.
* Always returns false if the kernel hasn't been initialized yet.
*/
function hasPermission(address _who, address _where, bytes32 _what, bytes _how) public view returns (bool) {
IACL defaultAcl = acl();
return address(defaultAcl) != address(0) && // Poor man's initialization check (saves gas)
defaultAcl.hasPermission(_who, _where, _what, _how);
}
function _setApp(bytes32 _namespace, bytes32 _appId, address _app) internal {
require(isContract(_app), ERROR_APP_NOT_CONTRACT);
apps[_namespace][_appId] = _app;
emit SetApp(_namespace, _appId, _app);
}
function _setAppIfNew(bytes32 _namespace, bytes32 _appId, address _app) internal {
address app = getApp(_namespace, _appId);
if (app != address(0)) {
// The only way to set an app is if it passes the isContract check, so no need to check it again
require(app == _app, ERROR_INVALID_APP_CHANGE);
} else {
_setApp(_namespace, _appId, _app);
}
}
modifier auth(bytes32 _role, uint256[] memory _params) {
require(
hasPermission(msg.sender, address(this), _role, ConversionHelpers.dangerouslyCastUintArrayToBytes(_params)),
ERROR_AUTH_FAILED
);
_;
}
}File 4 of 4: Lido
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.24;
/**
* @title Deposit contract interface
*/
interface IDepositContract {
/**
* @notice Top-ups deposit of a validator on the ETH 2.0 side
* @param pubkey Validator signing key
* @param withdrawal_credentials Credentials that allows to withdraw funds
* @param signature Signature of the request
* @param deposit_data_root The deposits Merkle tree node, used as a checksum
*/
function deposit(
bytes /* 48 */ pubkey,
bytes /* 32 */ withdrawal_credentials,
bytes /* 96 */ signature,
bytes32 deposit_data_root
)
external payable;
}
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
/* See contracts/COMPILERS.md */
pragma solidity 0.4.24;
import "@aragon/os/contracts/apps/AragonApp.sol";
import "@aragon/os/contracts/lib/math/SafeMath.sol";
import "@aragon/os/contracts/lib/math/SafeMath64.sol";
import "@aragon/os/contracts/common/IsContract.sol";
import "solidity-bytes-utils/contracts/BytesLib.sol";
import "./interfaces/ILido.sol";
import "./interfaces/INodeOperatorsRegistry.sol";
import "./interfaces/IDepositContract.sol";
import "./StETH.sol";
/**
* @title Liquid staking pool implementation
*
* Lido is an Ethereum 2.0 liquid staking protocol solving the problem of frozen staked Ethers
* until transfers become available in Ethereum 2.0.
* Whitepaper: https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf
*
* NOTE: the code below assumes moderate amount of node operators, e.g. up to 50.
*
* Since balances of all token holders change when the amount of total pooled Ether
* changes, this token cannot fully implement ERC20 standard: it only emits `Transfer`
* events upon explicit transfer between holders. In contrast, when Lido oracle reports
* rewards, no Transfer events are generated: doing so would require emitting an event
* for each token holder and thus running an unbounded loop.
*/
contract Lido is ILido, IsContract, StETH, AragonApp {
using SafeMath for uint256;
using SafeMath64 for uint64;
using UnstructuredStorage for bytes32;
/// ACL
bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE");
bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE");
bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY");
bytes32 constant public SET_ORACLE = keccak256("SET_ORACLE");
bytes32 constant public BURN_ROLE = keccak256("BURN_ROLE");
bytes32 constant public SET_TREASURY = keccak256("SET_TREASURY");
bytes32 constant public SET_INSURANCE_FUND = keccak256("SET_INSURANCE_FUND");
bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE");
uint256 constant public PUBKEY_LENGTH = 48;
uint256 constant public WITHDRAWAL_CREDENTIALS_LENGTH = 32;
uint256 constant public SIGNATURE_LENGTH = 96;
uint256 constant public DEPOSIT_SIZE = 32 ether;
uint256 internal constant DEPOSIT_AMOUNT_UNIT = 1000000000 wei;
/// @dev default value for maximum number of Ethereum 2.0 validators registered in a single depositBufferedEther call
uint256 internal constant DEFAULT_MAX_DEPOSITS_PER_CALL = 150;
bytes32 internal constant FEE_POSITION = keccak256("lido.Lido.fee");
bytes32 internal constant TREASURY_FEE_POSITION = keccak256("lido.Lido.treasuryFee");
bytes32 internal constant INSURANCE_FEE_POSITION = keccak256("lido.Lido.insuranceFee");
bytes32 internal constant NODE_OPERATORS_FEE_POSITION = keccak256("lido.Lido.nodeOperatorsFee");
bytes32 internal constant DEPOSIT_CONTRACT_POSITION = keccak256("lido.Lido.depositContract");
bytes32 internal constant ORACLE_POSITION = keccak256("lido.Lido.oracle");
bytes32 internal constant NODE_OPERATORS_REGISTRY_POSITION = keccak256("lido.Lido.nodeOperatorsRegistry");
bytes32 internal constant TREASURY_POSITION = keccak256("lido.Lido.treasury");
bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund");
/// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance
bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther");
/// @dev number of deposited validators (incrementing counter of deposit operations).
bytes32 internal constant DEPOSITED_VALIDATORS_POSITION = keccak256("lido.Lido.depositedValidators");
/// @dev total amount of Beacon-side Ether (sum of all the balances of Lido validators)
bytes32 internal constant BEACON_BALANCE_POSITION = keccak256("lido.Lido.beaconBalance");
/// @dev number of Lido's validators available in the Beacon state
bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators");
/// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side
bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.Lido.withdrawalCredentials");
/**
* @dev As AragonApp, Lido contract must be initialized with following variables:
* @param depositContract official ETH2 Deposit contract
* @param _oracle oracle contract
* @param _operators instance of Node Operators Registry
*/
function initialize(
IDepositContract depositContract,
address _oracle,
INodeOperatorsRegistry _operators,
address _treasury,
address _insuranceFund
)
public onlyInit
{
_setDepositContract(depositContract);
_setOracle(_oracle);
_setOperators(_operators);
_setTreasury(_treasury);
_setInsuranceFund(_insuranceFund);
initialized();
}
/**
* @notice Send funds to the pool
* @dev Users are able to submit their funds by transacting to the fallback function.
* Unlike vanilla Eth2.0 Deposit contract, accepting only 32-Ether transactions, Lido
* accepts payments of any size. Submitted Ethers are stored in Buffer until someone calls
* depositBufferedEther() and pushes them to the ETH2 Deposit contract.
*/
function() external payable {
// protection against accidental submissions by calling non-existent function
require(msg.data.length == 0, "NON_EMPTY_DATA");
_submit(0);
}
/**
* @notice Send funds to the pool with optional _referral parameter
* @dev This function is alternative way to submit funds. Supports optional referral address.
* @return Amount of StETH shares generated
*/
function submit(address _referral) external payable returns (uint256) {
return _submit(_referral);
}
/**
* @notice Deposits buffered ethers to the official DepositContract.
* @dev This function is separated from submit() to reduce the cost of sending funds.
*/
function depositBufferedEther() external auth(DEPOSIT_ROLE) {
return _depositBufferedEther(DEFAULT_MAX_DEPOSITS_PER_CALL);
}
/**
* @notice Deposits buffered ethers to the official DepositContract, making no more than `_maxDeposits` deposit calls.
* @dev This function is separated from submit() to reduce the cost of sending funds.
*/
function depositBufferedEther(uint256 _maxDeposits) external auth(DEPOSIT_ROLE) {
return _depositBufferedEther(_maxDeposits);
}
function burnShares(address _account, uint256 _sharesAmount)
external
authP(BURN_ROLE, arr(_account, _sharesAmount))
returns (uint256 newTotalShares)
{
return _burnShares(_account, _sharesAmount);
}
/**
* @notice Stop pool routine operations
*/
function stop() external auth(PAUSE_ROLE) {
_stop();
}
/**
* @notice Resume pool routine operations
*/
function resume() external auth(PAUSE_ROLE) {
_resume();
}
/**
* @notice Set fee rate to `_feeBasisPoints` basis points. The fees are accrued when oracles report staking results
* @param _feeBasisPoints Fee rate, in basis points
*/
function setFee(uint16 _feeBasisPoints) external auth(MANAGE_FEE) {
_setBPValue(FEE_POSITION, _feeBasisPoints);
emit FeeSet(_feeBasisPoints);
}
/**
* @notice Set fee distribution: `_treasuryFeeBasisPoints` basis points go to the treasury, `_insuranceFeeBasisPoints` basis points go to the insurance fund, `_operatorsFeeBasisPoints` basis points go to node operators. The sum has to be 10 000.
*/
function setFeeDistribution(
uint16 _treasuryFeeBasisPoints,
uint16 _insuranceFeeBasisPoints,
uint16 _operatorsFeeBasisPoints
)
external auth(MANAGE_FEE)
{
require(
10000 == uint256(_treasuryFeeBasisPoints)
.add(uint256(_insuranceFeeBasisPoints))
.add(uint256(_operatorsFeeBasisPoints)),
"FEES_DONT_ADD_UP"
);
_setBPValue(TREASURY_FEE_POSITION, _treasuryFeeBasisPoints);
_setBPValue(INSURANCE_FEE_POSITION, _insuranceFeeBasisPoints);
_setBPValue(NODE_OPERATORS_FEE_POSITION, _operatorsFeeBasisPoints);
emit FeeDistributionSet(_treasuryFeeBasisPoints, _insuranceFeeBasisPoints, _operatorsFeeBasisPoints);
}
/**
* @notice Set authorized oracle contract address to `_oracle`
* @dev Contract specified here is allowed to make periodical updates of beacon states
* by calling pushBeacon.
* @param _oracle oracle contract
*/
function setOracle(address _oracle) external auth(SET_ORACLE) {
_setOracle(_oracle);
}
/**
* @notice Set treasury contract address to `_treasury`
* @dev Contract specified here is used to accumulate the protocol treasury fee.
* @param _treasury contract which accumulates treasury fee.
*/
function setTreasury(address _treasury) external auth(SET_TREASURY) {
_setTreasury(_treasury);
}
/**
* @notice Set insuranceFund contract address to `_insuranceFund`
* @dev Contract specified here is used to accumulate the protocol insurance fee.
* @param _insuranceFund contract which accumulates insurance fee.
*/
function setInsuranceFund(address _insuranceFund) external auth(SET_INSURANCE_FUND) {
_setInsuranceFund(_insuranceFund);
}
/**
* @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 hash of withdrawal multisignature key as accepted by
* the deposit_contract.deposit function
*/
function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external auth(MANAGE_WITHDRAWAL_KEY) {
WITHDRAWAL_CREDENTIALS_POSITION.setStorageBytes32(_withdrawalCredentials);
getOperators().trimUnusedKeys();
emit WithdrawalCredentialsSet(_withdrawalCredentials);
}
/**
* @notice Issues withdrawal request. Not implemented.
* @param _amount Amount of StETH to withdraw
* @param _pubkeyHash Receiving address
*/
function withdraw(uint256 _amount, bytes32 _pubkeyHash) external whenNotStopped { /* solhint-disable-line no-unused-vars */
//will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2021 or 2022).
//at the moment withdrawals are not possible in the beacon chain and there's no workaround
revert("NOT_IMPLEMENTED_YET");
}
/**
* @notice Updates the number of Lido-controlled keys in the beacon validators set and their total balance.
* @dev periodically called by the Oracle contract
* @param _beaconValidators number of Lido's keys in the beacon state
* @param _beaconBalance simmarized balance of Lido-controlled keys in wei
*/
function pushBeacon(uint256 _beaconValidators, uint256 _beaconBalance) external whenNotStopped {
require(msg.sender == getOracle(), "APP_AUTH_FAILED");
uint256 depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256();
require(_beaconValidators <= depositedValidators, "REPORTED_MORE_DEPOSITED");
uint256 beaconValidators = BEACON_VALIDATORS_POSITION.getStorageUint256();
// Since the calculation of funds in the ingress queue is based on the number of validators
// that are in a transient state (deposited but not seen on beacon yet), we can't decrease the previously
// reported number (we'll be unable to figure out who is in the queue and count them).
// See LIP-1 for details https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-1.md
require(_beaconValidators >= beaconValidators, "REPORTED_LESS_VALIDATORS");
uint256 appearedValidators = _beaconValidators.sub(beaconValidators);
// RewardBase is the amount of money that is not included in the reward calculation
// Just appeared validators * 32 added to the previously reported beacon balance
uint256 rewardBase = (appearedValidators.mul(DEPOSIT_SIZE)).add(BEACON_BALANCE_POSITION.getStorageUint256());
// Save the current beacon balance and validators to
// calcuate rewards on the next push
BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance);
BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators);
if (_beaconBalance > rewardBase) {
uint256 rewards = _beaconBalance.sub(rewardBase);
distributeRewards(rewards);
}
}
/**
* @notice Send funds to recovery Vault. Overrides default AragonApp behaviour.
* @param _token Token to be sent to recovery vault.
*/
function transferToVault(address _token) external {
require(allowRecoverability(_token), "RECOVER_DISALLOWED");
address vault = getRecoveryVault();
require(isContract(vault), "RECOVER_VAULT_NOT_CONTRACT");
uint256 balance;
if (_token == ETH) {
balance = _getUnaccountedEther();
// Transfer replaced by call to prevent transfer gas amount issue
require(vault.call.value(balance)(), "RECOVER_TRANSFER_FAILED");
} else {
ERC20 token = ERC20(_token);
balance = token.staticBalanceOf(this);
// safeTransfer comes from overriden default implementation
require(token.safeTransfer(vault, balance), "RECOVER_TOKEN_TRANSFER_FAILED");
}
emit RecoverToVault(vault, _token, balance);
}
/**
* @notice Returns staking rewards fee rate
*/
function getFee() external view returns (uint16 feeBasisPoints) {
return _getFee();
}
/**
* @notice Returns fee distribution proportion
*/
function getFeeDistribution()
external
view
returns (
uint16 treasuryFeeBasisPoints,
uint16 insuranceFeeBasisPoints,
uint16 operatorsFeeBasisPoints
)
{
return _getFeeDistribution();
}
/**
* @notice Returns current credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched
*/
function getWithdrawalCredentials() public view returns (bytes32) {
return WITHDRAWAL_CREDENTIALS_POSITION.getStorageBytes32();
}
/**
* @notice Get the amount of Ether temporary buffered on this contract balance
* @dev Buffered balance is kept on the contract from the moment the funds are received from user
* until the moment they are actually sent to the official Deposit contract.
* @return uint256 of buffered funds in wei
*/
function getBufferedEther() external view returns (uint256) {
return _getBufferedEther();
}
/**
* @notice Gets deposit contract handle
*/
function getDepositContract() public view returns (IDepositContract) {
return IDepositContract(DEPOSIT_CONTRACT_POSITION.getStorageAddress());
}
/**
* @notice Gets authorized oracle address
* @return address of oracle contract
*/
function getOracle() public view returns (address) {
return ORACLE_POSITION.getStorageAddress();
}
/**
* @notice Gets node operators registry interface handle
*/
function getOperators() public view returns (INodeOperatorsRegistry) {
return INodeOperatorsRegistry(NODE_OPERATORS_REGISTRY_POSITION.getStorageAddress());
}
/**
* @notice Returns the treasury address
*/
function getTreasury() public view returns (address) {
return TREASURY_POSITION.getStorageAddress();
}
/**
* @notice Returns the insurance fund address
*/
function getInsuranceFund() public view returns (address) {
return INSURANCE_FUND_POSITION.getStorageAddress();
}
/**
* @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() public view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance) {
depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256();
beaconValidators = BEACON_VALIDATORS_POSITION.getStorageUint256();
beaconBalance = BEACON_BALANCE_POSITION.getStorageUint256();
}
/**
* @dev Sets the address of Deposit contract
* @param _contract the address of Deposit contract
*/
function _setDepositContract(IDepositContract _contract) internal {
require(isContract(address(_contract)), "NOT_A_CONTRACT");
DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_contract));
}
/**
* @dev Internal function to set authorized oracle address
* @param _oracle oracle contract
*/
function _setOracle(address _oracle) internal {
require(isContract(_oracle), "NOT_A_CONTRACT");
ORACLE_POSITION.setStorageAddress(_oracle);
}
/**
* @dev Internal function to set node operator registry address
* @param _r registry of node operators
*/
function _setOperators(INodeOperatorsRegistry _r) internal {
require(isContract(_r), "NOT_A_CONTRACT");
NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(_r);
}
function _setTreasury(address _treasury) internal {
require(_treasury != address(0), "SET_TREASURY_ZERO_ADDRESS");
TREASURY_POSITION.setStorageAddress(_treasury);
}
function _setInsuranceFund(address _insuranceFund) internal {
require(_insuranceFund != address(0), "SET_INSURANCE_FUND_ZERO_ADDRESS");
INSURANCE_FUND_POSITION.setStorageAddress(_insuranceFund);
}
/**
* @dev Process user deposit, mints liquid tokens and increase the pool buffer
* @param _referral address of referral.
* @return amount of StETH shares generated
*/
function _submit(address _referral) internal whenNotStopped returns (uint256) {
address sender = msg.sender;
uint256 deposit = msg.value;
require(deposit != 0, "ZERO_DEPOSIT");
uint256 sharesAmount = getSharesByPooledEth(deposit);
if (sharesAmount == 0) {
// totalControlledEther is 0: either the first-ever deposit or complete slashing
// assume that shares correspond to Ether 1-to-1
sharesAmount = deposit;
}
_mintShares(sender, sharesAmount);
_submitted(sender, deposit, _referral);
_emitTransferAfterMintingShares(sender, sharesAmount);
return sharesAmount;
}
/**
* @dev Emits an {Transfer} event where from is 0 address. Indicates mint events.
*/
function _emitTransferAfterMintingShares(address _to, uint256 _sharesAmount) internal {
emit Transfer(address(0), _to, getPooledEthByShares(_sharesAmount));
}
/**
* @dev Deposits buffered eth to the DepositContract and assigns chunked deposits to node operators
*/
function _depositBufferedEther(uint256 _maxDeposits) internal whenNotStopped {
uint256 buffered = _getBufferedEther();
if (buffered >= DEPOSIT_SIZE) {
uint256 unaccounted = _getUnaccountedEther();
uint256 numDeposits = buffered.div(DEPOSIT_SIZE);
_markAsUnbuffered(_ETH2Deposit(numDeposits < _maxDeposits ? numDeposits : _maxDeposits));
assert(_getUnaccountedEther() == unaccounted);
}
}
/**
* @dev Performs deposits to the ETH 2.0 side
* @param _numDeposits Number of deposits to perform
* @return actually deposited Ether amount
*/
function _ETH2Deposit(uint256 _numDeposits) internal returns (uint256) {
(bytes memory pubkeys, bytes memory signatures) = getOperators().assignNextSigningKeys(_numDeposits);
if (pubkeys.length == 0) {
return 0;
}
require(pubkeys.length.mod(PUBKEY_LENGTH) == 0, "REGISTRY_INCONSISTENT_PUBKEYS_LEN");
require(signatures.length.mod(SIGNATURE_LENGTH) == 0, "REGISTRY_INCONSISTENT_SIG_LEN");
uint256 numKeys = pubkeys.length.div(PUBKEY_LENGTH);
require(numKeys == signatures.length.div(SIGNATURE_LENGTH), "REGISTRY_INCONSISTENT_SIG_COUNT");
for (uint256 i = 0; i < numKeys; ++i) {
bytes memory pubkey = BytesLib.slice(pubkeys, i * PUBKEY_LENGTH, PUBKEY_LENGTH);
bytes memory signature = BytesLib.slice(signatures, i * SIGNATURE_LENGTH, SIGNATURE_LENGTH);
_stake(pubkey, signature);
}
DEPOSITED_VALIDATORS_POSITION.setStorageUint256(
DEPOSITED_VALIDATORS_POSITION.getStorageUint256().add(numKeys)
);
return numKeys.mul(DEPOSIT_SIZE);
}
/**
* @dev Invokes a deposit call to the official Deposit contract
* @param _pubkey Validator to stake for
* @param _signature Signature of the deposit call
*/
function _stake(bytes memory _pubkey, bytes memory _signature) internal {
bytes32 withdrawalCredentials = getWithdrawalCredentials();
require(withdrawalCredentials != 0, "EMPTY_WITHDRAWAL_CREDENTIALS");
uint256 value = DEPOSIT_SIZE;
// The following computations and Merkle tree-ization will make official Deposit contract happy
uint256 depositAmount = value.div(DEPOSIT_AMOUNT_UNIT);
assert(depositAmount.mul(DEPOSIT_AMOUNT_UNIT) == value); // properly rounded
// Compute deposit data root (`DepositData` hash tree root) according to deposit_contract.sol
bytes32 pubkeyRoot = sha256(_pad64(_pubkey));
bytes32 signatureRoot = sha256(
abi.encodePacked(
sha256(BytesLib.slice(_signature, 0, 64)),
sha256(_pad64(BytesLib.slice(_signature, 64, SIGNATURE_LENGTH.sub(64))))
)
);
bytes32 depositDataRoot = sha256(
abi.encodePacked(
sha256(abi.encodePacked(pubkeyRoot, withdrawalCredentials)),
sha256(abi.encodePacked(_toLittleEndian64(depositAmount), signatureRoot))
)
);
uint256 targetBalance = address(this).balance.sub(value);
getDepositContract().deposit.value(value)(
_pubkey, abi.encodePacked(withdrawalCredentials), _signature, depositDataRoot);
require(address(this).balance == targetBalance, "EXPECTING_DEPOSIT_TO_HAPPEN");
}
/**
* @dev Distributes rewards by minting and distributing corresponding amount of liquid tokens.
* @param _totalRewards Total rewards accrued on the Ethereum 2.0 side in wei
*/
function distributeRewards(uint256 _totalRewards) internal {
// We need to take a defined percentage of the reported reward as a fee, and we do
// this by minting new token shares and assigning them to the fee recipients (see
// StETH docs for the explanation of the shares mechanics). The staking rewards fee
// is defined in basis points (1 basis point is equal to 0.01%, 10000 is 100%).
//
// Since we've increased totalPooledEther by _totalRewards (which is already
// performed by the time this function is called), the combined cost of all holders'
// shares has became _totalRewards StETH tokens more, effectively splitting the reward
// between each token holder proportionally to their token share.
//
// Now we want to mint new shares to the fee recipient, so that the total cost of the
// newly-minted shares exactly corresponds to the fee taken:
//
// shares2mint * newShareCost = (_totalRewards * feeBasis) / 10000
// newShareCost = newTotalPooledEther / (prevTotalShares + shares2mint)
//
// which follows to:
//
// _totalRewards * feeBasis * prevTotalShares
// shares2mint = --------------------------------------------------------------
// (newTotalPooledEther * 10000) - (feeBasis * _totalRewards)
//
// The effect is that the given percentage of the reward goes to the fee recipient, and
// the rest of the reward is distributed between token holders proportionally to their
// token shares.
uint256 feeBasis = _getFee();
uint256 shares2mint = (
_totalRewards.mul(feeBasis).mul(_getTotalShares())
.div(
_getTotalPooledEther().mul(10000)
.sub(feeBasis.mul(_totalRewards))
)
);
// Mint the calculated amount of shares to this contract address. This will reduce the
// balances of the holders, as if the fee was taken in parts from each of them.
_mintShares(address(this), shares2mint);
(,uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints) = _getFeeDistribution();
uint256 toInsuranceFund = shares2mint.mul(insuranceFeeBasisPoints).div(10000);
address insuranceFund = getInsuranceFund();
_transferShares(address(this), insuranceFund, toInsuranceFund);
_emitTransferAfterMintingShares(insuranceFund, toInsuranceFund);
uint256 distributedToOperatorsShares = _distributeNodeOperatorsReward(
shares2mint.mul(operatorsFeeBasisPoints).div(10000)
);
// Transfer the rest of the fee to treasury
uint256 toTreasury = shares2mint.sub(toInsuranceFund).sub(distributedToOperatorsShares);
address treasury = getTreasury();
_transferShares(address(this), treasury, toTreasury);
_emitTransferAfterMintingShares(treasury, toTreasury);
}
function _distributeNodeOperatorsReward(uint256 _sharesToDistribute) internal returns (uint256 distributed) {
(address[] memory recipients, uint256[] memory shares) = getOperators().getRewardsDistribution(_sharesToDistribute);
assert(recipients.length == shares.length);
distributed = 0;
for (uint256 idx = 0; idx < recipients.length; ++idx) {
_transferShares(
address(this),
recipients[idx],
shares[idx]
);
_emitTransferAfterMintingShares(recipients[idx], shares[idx]);
distributed = distributed.add(shares[idx]);
}
}
/**
* @dev Records a deposit made by a user with optional referral
* @param _sender sender's address
* @param _value Deposit value in wei
* @param _referral address of the referral
*/
function _submitted(address _sender, uint256 _value, address _referral) internal {
BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(_value));
emit Submitted(_sender, _value, _referral);
}
/**
* @dev Records a deposit to the deposit_contract.deposit function.
* @param _amount Total amount deposited to the ETH 2.0 side
*/
function _markAsUnbuffered(uint256 _amount) internal {
BUFFERED_ETHER_POSITION.setStorageUint256(
BUFFERED_ETHER_POSITION.getStorageUint256().sub(_amount));
emit Unbuffered(_amount);
}
/**
* @dev Write a value nominated in basis points
*/
function _setBPValue(bytes32 _slot, uint16 _value) internal {
require(_value <= 10000, "VALUE_OVER_100_PERCENT");
_slot.setStorageUint256(uint256(_value));
}
/**
* @dev Returns staking rewards fee rate
*/
function _getFee() internal view returns (uint16) {
return _readBPValue(FEE_POSITION);
}
/**
* @dev Returns fee distribution proportion
*/
function _getFeeDistribution() internal view
returns (uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints)
{
treasuryFeeBasisPoints = _readBPValue(TREASURY_FEE_POSITION);
insuranceFeeBasisPoints = _readBPValue(INSURANCE_FEE_POSITION);
operatorsFeeBasisPoints = _readBPValue(NODE_OPERATORS_FEE_POSITION);
}
/**
* @dev Read a value nominated in basis points
*/
function _readBPValue(bytes32 _slot) internal view returns (uint16) {
uint256 v = _slot.getStorageUint256();
assert(v <= 10000);
return uint16(v);
}
/**
* @dev Gets the amount of Ether temporary buffered on this contract balance
*/
function _getBufferedEther() internal view returns (uint256) {
uint256 buffered = BUFFERED_ETHER_POSITION.getStorageUint256();
assert(address(this).balance >= buffered);
return buffered;
}
/**
* @dev Gets unaccounted (excess) Ether on this contract balance
*/
function _getUnaccountedEther() internal view returns (uint256) {
return address(this).balance.sub(_getBufferedEther());
}
/**
* @dev Calculates and returns the total base balance (multiple of 32) of validators in transient state,
* i.e. submitted to the official Deposit contract but not yet visible in the beacon state.
* @return transient balance in wei (1e-18 Ether)
*/
function _getTransientBalance() internal view returns (uint256) {
uint256 depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256();
uint256 beaconValidators = BEACON_VALIDATORS_POSITION.getStorageUint256();
// beaconValidators can never be less than deposited ones.
assert(depositedValidators >= beaconValidators);
uint256 transientValidators = depositedValidators.sub(beaconValidators);
return transientValidators.mul(DEPOSIT_SIZE);
}
/**
* @dev Gets the total amount of Ether controlled by the system
* @return total balance in wei
*/
function _getTotalPooledEther() internal view returns (uint256) {
uint256 bufferedBalance = _getBufferedEther();
uint256 beaconBalance = BEACON_BALANCE_POSITION.getStorageUint256();
uint256 transientBalance = _getTransientBalance();
return bufferedBalance.add(beaconBalance).add(transientBalance);
}
/**
* @dev Padding memory array with zeroes up to 64 bytes on the right
* @param _b Memory array of size 32 .. 64
*/
function _pad64(bytes memory _b) internal pure returns (bytes memory) {
assert(_b.length >= 32 && _b.length <= 64);
if (64 == _b.length)
return _b;
bytes memory zero32 = new bytes(32);
assembly { mstore(add(zero32, 0x20), 0) }
if (32 == _b.length)
return BytesLib.concat(_b, zero32);
else
return BytesLib.concat(_b, BytesLib.slice(zero32, 0, uint256(64).sub(_b.length)));
}
/**
* @dev Converting value to little endian bytes and padding up to 32 bytes on the right
* @param _value Number less than `2**64` for compatibility reasons
*/
function _toLittleEndian64(uint256 _value) internal pure returns (uint256 result) {
result = 0;
uint256 temp_value = _value;
for (uint256 i = 0; i < 8; ++i) {
result = (result << 8) | (temp_value & 0xFF);
temp_value >>= 8;
}
assert(0 == temp_value); // fully converted
result <<= (24 * 8);
}
function to64(uint256 v) internal pure returns (uint64) {
assert(v <= uint256(uint64(-1)));
return uint64(v);
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "./AppStorage.sol";
import "../acl/ACLSyntaxSugar.sol";
import "../common/Autopetrified.sol";
import "../common/ConversionHelpers.sol";
import "../common/ReentrancyGuard.sol";
import "../common/VaultRecoverable.sol";
import "../evmscript/EVMScriptRunner.sol";
// 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
}
}
// 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;
}
}
// See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/d51e38758e1d985661534534d5c61e27bece5042/contracts/math/SafeMath.sol
// Adapted for uint64, pragma ^0.4.24, and satisfying our linter rules
// Also optimized the mul() implementation, see https://github.com/aragon/aragonOS/pull/417
pragma solidity ^0.4.24;
/**
* @title SafeMath64
* @dev Math operations for uint64 with safety checks that revert on error
*/
library SafeMath64 {
string private constant ERROR_ADD_OVERFLOW = "MATH64_ADD_OVERFLOW";
string private constant ERROR_SUB_UNDERFLOW = "MATH64_SUB_UNDERFLOW";
string private constant ERROR_MUL_OVERFLOW = "MATH64_MUL_OVERFLOW";
string private constant ERROR_DIV_ZERO = "MATH64_DIV_ZERO";
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint64 _a, uint64 _b) internal pure returns (uint64) {
uint256 c = uint256(_a) * uint256(_b);
require(c < 0x010000000000000000, ERROR_MUL_OVERFLOW); // 2**64 (less gas this way)
return uint64(c);
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint64 _a, uint64 _b) internal pure returns (uint64) {
require(_b > 0, ERROR_DIV_ZERO); // Solidity only automatically asserts when dividing by 0
uint64 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(uint64 _a, uint64 _b) internal pure returns (uint64) {
require(_b <= _a, ERROR_SUB_UNDERFLOW);
uint64 c = _a - _b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint64 _a, uint64 _b) internal pure returns (uint64) {
uint64 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(uint64 a, uint64 b) internal pure returns (uint64) {
require(b != 0, ERROR_DIV_ZERO);
return a % b;
}
}
/*
* 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;
}
}
/*
* @title Solidity Bytes Arrays Utils
* @author Gonçalo Sá <goncalo.sa@consensys.net>
*
* @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
* The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
*/
pragma solidity ^0.4.19;
library BytesLib {
function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes) {
bytes memory tempBytes;
assembly {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// Store the length of the first bytes array at the beginning of
// the memory for tempBytes.
let length := mload(_preBytes)
mstore(tempBytes, length)
// Maintain a memory counter for the current write location in the
// temp bytes array by adding the 32 bytes for the array length to
// the starting location.
let mc := add(tempBytes, 0x20)
// Stop copying when the memory counter reaches the length of the
// first bytes array.
let end := add(mc, length)
for {
// Initialize a copy counter to the start of the _preBytes data,
// 32 bytes into its memory.
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
// Increase both counters by 32 bytes each iteration.
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// Write the _preBytes data into the tempBytes memory 32 bytes
// at a time.
mstore(mc, mload(cc))
}
// Add the length of _postBytes to the current length of tempBytes
// and store it as the new length in the first 32 bytes of the
// tempBytes memory.
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))
// Move the memory counter back from a multiple of 0x20 to the
// actual end of the _preBytes data.
mc := end
// Stop copying when the memory counter reaches the new combined
// length of the arrays.
end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
// next 32 byte block, then round down to the nearest multiple of
// 32. If the sum of the length of the two arrays is zero then add
// one before rounding down to leave a blank 32 bytes (the length block with 0).
mstore(0x40, and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31) // Round down to the nearest 32 bytes.
))
}
return tempBytes;
}
function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
assembly {
// Read the first 32 bytes of _preBytes storage, which is the length
// of the array. (We don't need to use the offset into the slot
// because arrays use the entire slot.)
let fslot := sload(_preBytes_slot)
// Arrays of 31 bytes or less have an even value in their slot,
// while longer arrays have an odd value. The actual length is
// the slot divided by two for odd values, and the lowest order
// byte divided by two for even values.
// If the slot is even, bitwise and the slot with 255 and divide by
// two to get the length. If the slot is odd, bitwise and the slot
// with -1 and divide by two.
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)
let newlength := add(slength, mlength)
// slength can contain both the length and contents of the array
// if length < 32 bytes so let's prepare for that
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
switch add(lt(slength, 32), lt(newlength, 32))
case 2 {
// Since the new array still fits in the slot, we just need to
// update the contents of the slot.
// uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
sstore(
_preBytes_slot,
// all the modifications to the slot are inside this
// next block
add(
// we can just add to the slot contents because the
// bytes we want to change are the LSBs
fslot,
add(
mul(
div(
// load the bytes from memory
mload(add(_postBytes, 0x20)),
// zero all bytes to the right
exp(0x100, sub(32, mlength))
),
// and now shift left the number of bytes to
// leave space for the length in the slot
exp(0x100, sub(32, newlength))
),
// increase length by the double of the memory
// bytes length
mul(mlength, 2)
)
)
)
}
case 1 {
// The stored value fits in the slot, but the combined value
// will exceed it.
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
// save new length
sstore(_preBytes_slot, add(mul(newlength, 2), 1))
// The contents of the _postBytes array start 32 bytes into
// the structure. Our first read should obtain the `submod`
// bytes that can fit into the unused space in the last word
// of the stored array. To get this, we read 32 bytes starting
// from `submod`, so the data we read overlaps with the array
// contents by `submod` bytes. Masking the lowest-order
// `submod` bytes allows us to add that value directly to the
// stored value.
let submod := sub(32, slength)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)
sstore(
sc,
add(
and(
fslot,
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
),
and(mload(mc), mask)
)
)
for {
mc := add(mc, 0x20)
sc := add(sc, 1)
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
sstore(sc, mload(mc))
}
mask := exp(0x100, sub(mc, end))
sstore(sc, mul(div(mload(mc), mask), mask))
}
default {
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
// Start copying to the last used word of the stored array.
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
// save new length
sstore(_preBytes_slot, add(mul(newlength, 2), 1))
// Copy over the first `submod` bytes of the new data as in
// case 1 above.
let slengthmod := mod(slength, 32)
let mlengthmod := mod(mlength, 32)
let submod := sub(32, slengthmod)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)
sstore(sc, add(sload(sc), and(mload(mc), mask)))
for {
sc := add(sc, 1)
mc := add(mc, 0x20)
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
sstore(sc, mload(mc))
}
mask := exp(0x100, sub(mc, end))
sstore(sc, mul(div(mload(mc), mask), mask))
}
}
}
function slice(bytes _bytes, uint _start, uint _length) internal pure returns (bytes) {
require(_bytes.length >= (_start + _length));
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function toAddress(bytes _bytes, uint _start) internal pure returns (address) {
require(_bytes.length >= (_start + 20));
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function toUint8(bytes _bytes, uint _start) internal pure returns (uint8) {
require(_bytes.length >= (_start + 1));
uint8 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}
return tempUint;
}
function toUint16(bytes _bytes, uint _start) internal pure returns (uint16) {
require(_bytes.length >= (_start + 2));
uint16 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x2), _start))
}
return tempUint;
}
function toUint32(bytes _bytes, uint _start) internal pure returns (uint32) {
require(_bytes.length >= (_start + 4));
uint32 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x4), _start))
}
return tempUint;
}
function toUint(bytes _bytes, uint _start) internal pure returns (uint256) {
require(_bytes.length >= (_start + 32));
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
function toBytes32(bytes _bytes, uint _start) internal pure returns (bytes32) {
require(_bytes.length >= (_start + 32));
bytes32 tempBytes32;
assembly {
tempBytes32 := mload(add(add(_bytes, 0x20), _start))
}
return tempBytes32;
}
function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
bool success = true;
assembly {
let length := mload(_preBytes)
// if lengths don't match the arrays are not equal
switch eq(length, mload(_postBytes))
case 1 {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1
let mc := add(_preBytes, 0x20)
let end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
} eq(add(lt(mc, end), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// if any of these checks fails then arrays are not equal
if iszero(eq(mload(mc), mload(cc))) {
// unsuccess:
success := 0
cb := 0
}
}
}
default {
// unsuccess:
success := 0
}
}
return success;
}
function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {
bool success = true;
assembly {
// we know _preBytes_offset is 0
let fslot := sload(_preBytes_slot)
// Decode the length of the stored array like in concatStorage().
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)
// if lengths don't match the arrays are not equal
switch eq(slength, mlength)
case 1 {
// slength can contain both the length and contents of the array
// if length < 32 bytes so let's prepare for that
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
if iszero(iszero(slength)) {
switch lt(slength, 32)
case 1 {
// blank the last byte which is the length
fslot := mul(div(fslot, 0x100), 0x100)
if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
// unsuccess:
success := 0
}
}
default {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
let sc := keccak256(0x0, 0x20)
let mc := add(_postBytes, 0x20)
let end := add(mc, mlength)
// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
for {} eq(add(lt(mc, end), cb), 2) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
if iszero(eq(sload(sc), mload(mc))) {
// unsuccess:
success := 0
cb := 0
}
}
}
}
}
default {
// unsuccess:
success := 0
}
}
return success;
}
}
// 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.
*/
interface ILido {
/**
* @notice Stop pool routine operations
*/
function stop() external;
/**
* @notice Resume pool routine operations
*/
function resume() external;
event Stopped();
event Resumed();
/**
* @notice Set fee rate to `_feeBasisPoints` basis points. The fees are accrued when oracles report staking results
* @param _feeBasisPoints Fee rate, in basis points
*/
function setFee(uint16 _feeBasisPoints) external;
/**
* @notice Set fee distribution: `_treasuryFeeBasisPoints` basis points go to the treasury, `_insuranceFeeBasisPoints` basis points go to the insurance fund, `_operatorsFeeBasisPoints` basis points go to node operators. 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 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 hash of withdrawal multisignature key as accepted by
* the deposit_contract.deposit function
*/
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);
/**
* @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 pushBeacon(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);
/**
* @notice Issues withdrawal request. Large withdrawals will be processed only after the phase 2 launch.
* @param _amount Amount of StETH to burn
* @param _pubkeyHash Receiving address
*/
function withdraw(uint256 _amount, bytes32 _pubkeyHash) external;
// 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);
}
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.24;
/**
* @title Node Operator registry
*
* Node Operator registry manages signing keys and other node operator data.
* It's also responsible for distributing rewards to node operators.
*/
interface INodeOperatorsRegistry {
/**
* @notice Add node operator named `name` with reward address `rewardAddress` and staking limit `stakingLimit` validators
* @param _name Human-readable name
* @param _rewardAddress Ethereum 1 address which receives stETH rewards for this operator
* @param _stakingLimit the maximum number of validators to stake for this operator
* @return a unique key of the added operator
*/
function addNodeOperator(string _name, address _rewardAddress, uint64 _stakingLimit) external returns (uint256 id);
/**
* @notice `_active ? 'Enable' : 'Disable'` the node operator #`_id`
*/
function setNodeOperatorActive(uint256 _id, bool _active) external;
/**
* @notice Change human-readable name of the node operator #`_id` to `_name`
*/
function setNodeOperatorName(uint256 _id, string _name) external;
/**
* @notice Change reward address of the node operator #`_id` to `_rewardAddress`
*/
function setNodeOperatorRewardAddress(uint256 _id, address _rewardAddress) external;
/**
* @notice Set the maximum number of validators to stake for the node operator #`_id` to `_stakingLimit`
*/
function setNodeOperatorStakingLimit(uint256 _id, uint64 _stakingLimit) external;
/**
* @notice Report `_stoppedIncrement` more stopped validators of the node operator #`_id`
*/
function reportStoppedValidators(uint256 _id, uint64 _stoppedIncrement) external;
/**
* @notice Remove unused signing keys
* @dev Function is used by the pool
*/
function trimUnusedKeys() external;
/**
* @notice Returns total number of node operators
*/
function getNodeOperatorsCount() external view returns (uint256);
/**
* @notice Returns number of active node operators
*/
function getActiveNodeOperatorsCount() external view returns (uint256);
/**
* @notice Returns the n-th node operator
* @param _id Node Operator id
* @param _fullInfo If true, name will be returned as well
*/
function getNodeOperator(uint256 _id, bool _fullInfo) external view returns (
bool active,
string name,
address rewardAddress,
uint64 stakingLimit,
uint64 stoppedValidators,
uint64 totalSigningKeys,
uint64 usedSigningKeys);
/**
* @notice Returns the rewards distribution proportional to the effective stake for each node operator.
* @param _totalRewardShares Total amount of reward shares to distribute.
*/
function getRewardsDistribution(uint256 _totalRewardShares) external view returns (
address[] memory recipients,
uint256[] memory shares
);
event NodeOperatorAdded(uint256 id, string name, address rewardAddress, uint64 stakingLimit);
event NodeOperatorActiveSet(uint256 indexed id, bool active);
event NodeOperatorNameSet(uint256 indexed id, string name);
event NodeOperatorRewardAddressSet(uint256 indexed id, address rewardAddress);
event NodeOperatorStakingLimitSet(uint256 indexed id, uint64 stakingLimit);
event NodeOperatorTotalStoppedValidatorsReported(uint256 indexed id, uint64 totalStopped);
/**
* @notice Selects and returns at most `_numKeys` signing keys (as well as the corresponding
* signatures) from the set of active keys and marks the selected keys as used.
* May only be called by the pool contract.
*
* @param _numKeys The number of keys to select. The actual number of selected keys may be less
* due to the lack of active keys.
*/
function assignNextSigningKeys(uint256 _numKeys) external returns (bytes memory pubkeys, bytes memory signatures);
/**
* @notice Add `_quantity` validator signing keys to the keys of the node operator #`_operator_id`. Concatenated keys are: `_pubkeys`
* @dev Along with each key the DAO has to provide a signatures for the
* (pubkey, withdrawal_credentials, 32000000000) message.
* Given that information, the contract'll be able to call
* deposit_contract.deposit on-chain.
* @param _operator_id Node Operator id
* @param _quantity Number of signing keys provided
* @param _pubkeys Several concatenated validator signing keys
* @param _signatures Several concatenated signatures for (pubkey, withdrawal_credentials, 32000000000) messages
*/
function addSigningKeys(uint256 _operator_id, uint256 _quantity, bytes _pubkeys, bytes _signatures) external;
/**
* @notice Removes a validator signing key #`_index` from the keys of the node operator #`_operator_id`
* @param _operator_id Node Operator id
* @param _index Index of the key, starting with 0
*/
function removeSigningKey(uint256 _operator_id, uint256 _index) external;
/**
* @notice Returns total number of signing keys of the node operator #`_operator_id`
*/
function getTotalSigningKeyCount(uint256 _operator_id) external view returns (uint256);
/**
* @notice Returns number of usable signing keys of the node operator #`_operator_id`
*/
function getUnusedSigningKeyCount(uint256 _operator_id) external view returns (uint256);
/**
* @notice Returns n-th signing key of the node operator #`_operator_id`
* @param _operator_id Node Operator id
* @param _index Index of the key, starting with 0
* @return key Key
* @return depositSignature Signature needed for a deposit_contract.deposit call
* @return used Flag indication if the key was used in the staking
*/
function getSigningKey(uint256 _operator_id, uint256 _index) external view returns
(bytes key, bytes depositSignature, bool used);
event SigningKeyAdded(uint256 indexed operatorId, bytes pubkey);
event SigningKeyRemoved(uint256 indexed operatorId, bytes pubkey);
}
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
/* See contracts/COMPILERS.md */
pragma solidity 0.4.24;
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "@aragon/os/contracts/common/UnstructuredStorage.sol";
import "@aragon/os/contracts/lib/math/SafeMath.sol";
import "./lib/Pausable.sol";
/**
* @title Interest-bearing ERC20-like token for Lido Liquid Stacking protocol.
*
* This contract is abstract. To make the contract deployable override the
* `_getTotalPooledEther` function. `Lido.sol` contract inherits StETH and defines
* the `_getTotalPooledEther` function.
*
* StETH balances are dynamic and represent the holder's share in the total amount
* of Ether controlled by the protocol. Account shares aren't normalized, so the
* contract also stores the sum of all shares to calculate each account's token balance
* which equals to:
*
* shares[account] * _getTotalPooledEther() / _getTotalShares()
*
* For example, assume that we have:
*
* _getTotalPooledEther() -> 10 ETH
* sharesOf(user1) -> 100
* sharesOf(user2) -> 400
*
* Therefore:
*
* balanceOf(user1) -> 2 tokens which corresponds 2 ETH
* balanceOf(user2) -> 8 tokens which corresponds 8 ETH
*
* Since balances of all token holders change when the amount of total pooled Ether
* changes, this token cannot fully implement ERC20 standard: it only emits `Transfer`
* events upon explicit transfer between holders. In contrast, when total amount of
* pooled Ether increases, no `Transfer` events are generated: doing so would require
* emitting an event for each token holder and thus running an unbounded loop.
*
* The token inherits from `Pausable` and uses `whenNotStopped` modifier for methods
* which change `shares` or `allowances`. `_stop` and `_resume` functions are overriden
* in `Lido.sol` and might be called by an account with the `PAUSE_ROLE` assigned by the
* DAO. This is useful for emergency scenarios, e.g. a protocol bug, where one might want
* to freeze all token transfers and approvals until the emergency is resolved.
*/
contract StETH is IERC20, Pausable {
using SafeMath for uint256;
using UnstructuredStorage for bytes32;
/**
* @dev StETH balances are dynamic and are calculated based on the accounts' shares
* and the total amount of Ether controlled by the protocol. Account shares aren't
* normalized, so the contract also stores the sum of all shares to calculate
* each account's token balance which equals to:
*
* shares[account] * _getTotalPooledEther() / _getTotalShares()
*/
mapping (address => uint256) private shares;
/**
* @dev Allowances are nominated in tokens, not token shares.
*/
mapping (address => mapping (address => uint256)) private allowances;
/**
* @dev Storage position used for holding the total amount of shares in existence.
*
* The Lido protocol is built on top of Aragon and uses the Unstructured Storage pattern
* for value types:
*
* https://blog.openzeppelin.com/upgradeability-using-unstructured-storage
* https://blog.8bitzen.com/posts/20-02-2020-understanding-how-solidity-upgradeable-unstructured-proxies-work
*
* For reference types, conventional storage variables are used since it's non-trivial
* and error-prone to implement reference-type unstructured storage using Solidity v0.4;
* see https://github.com/lidofinance/lido-dao/issues/181#issuecomment-736098834
*/
bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("lido.StETH.totalShares");
/**
* @return the name of the token.
*/
function name() public pure returns (string) {
return "Liquid staked Ether 2.0";
}
/**
* @return the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public pure returns (string) {
return "stETH";
}
/**
* @return the number of decimals for getting user representation of a token amount.
*/
function decimals() public pure returns (uint8) {
return 18;
}
/**
* @return the amount of tokens in existence.
*
* @dev Always equals to `_getTotalPooledEther()` since token amount
* is pegged to the total amount of Ether controlled by the protocol.
*/
function totalSupply() public view returns (uint256) {
return _getTotalPooledEther();
}
/**
* @return the entire amount of Ether controlled by the protocol.
*
* @dev The sum of all ETH balances in the protocol, equals to the total supply of stETH.
*/
function getTotalPooledEther() public view returns (uint256) {
return _getTotalPooledEther();
}
/**
* @return the amount of tokens owned by the `_account`.
*
* @dev Balances are dynamic and equal the `_account`'s share in the amount of the
* total Ether controlled by the protocol. See `sharesOf`.
*/
function balanceOf(address _account) public view returns (uint256) {
return getPooledEthByShares(_sharesOf(_account));
}
/**
* @notice Moves `_amount` tokens from the caller's account to the `_recipient` account.
*
* @return a boolean value indicating whether the operation succeeded.
* Emits a `Transfer` event.
*
* Requirements:
*
* - `_recipient` cannot be the zero address.
* - the caller must have a balance of at least `_amount`.
* - the contract must not be paused.
*
* @dev The `_amount` argument is the amount of tokens, not shares.
*/
function transfer(address _recipient, uint256 _amount) public returns (bool) {
_transfer(msg.sender, _recipient, _amount);
return true;
}
/**
* @return the remaining number of tokens that `_spender` is allowed to spend
* on behalf of `_owner` through `transferFrom`. This is zero by default.
*
* @dev This value changes when `approve` or `transferFrom` is called.
*/
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowances[_owner][_spender];
}
/**
* @notice Sets `_amount` as the allowance of `_spender` over the caller's tokens.
*
* @return a boolean value indicating whether the operation succeeded.
* Emits an `Approval` event.
*
* Requirements:
*
* - `_spender` cannot be the zero address.
* - the contract must not be paused.
*
* @dev The `_amount` argument is the amount of tokens, not shares.
*/
function approve(address _spender, uint256 _amount) public returns (bool) {
_approve(msg.sender, _spender, _amount);
return true;
}
/**
* @notice Moves `_amount` tokens from `_sender` to `_recipient` using the
* allowance mechanism. `_amount` is then deducted from the caller's
* allowance.
*
* @return a boolean value indicating whether the operation succeeded.
*
* Emits a `Transfer` event.
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `_sender` and `_recipient` cannot be the zero addresses.
* - `_sender` must have a balance of at least `_amount`.
* - the caller must have allowance for `_sender`'s tokens of at least `_amount`.
* - the contract must not be paused.
*
* @dev The `_amount` argument is the amount of tokens, not shares.
*/
function transferFrom(address _sender, address _recipient, uint256 _amount) public returns (bool) {
uint256 currentAllowance = allowances[_sender][msg.sender];
require(currentAllowance >= _amount, "TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE");
_transfer(_sender, _recipient, _amount);
_approve(_sender, msg.sender, currentAllowance.sub(_amount));
return true;
}
/**
* @notice Atomically increases the allowance granted to `_spender` by the caller by `_addedValue`.
*
* This is an alternative to `approve` that can be used as a mitigation for
* problems described in:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `_spender` cannot be the the zero address.
* - the contract must not be paused.
*/
function increaseAllowance(address _spender, uint256 _addedValue) public returns (bool) {
_approve(msg.sender, _spender, allowances[msg.sender][_spender].add(_addedValue));
return true;
}
/**
* @notice Atomically decreases the allowance granted to `_spender` by the caller by `_subtractedValue`.
*
* This is an alternative to `approve` that can be used as a mitigation for
* problems described in:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `_spender` cannot be the zero address.
* - `_spender` must have allowance for the caller of at least `_subtractedValue`.
* - the contract must not be paused.
*/
function decreaseAllowance(address _spender, uint256 _subtractedValue) public returns (bool) {
uint256 currentAllowance = allowances[msg.sender][_spender];
require(currentAllowance >= _subtractedValue, "DECREASED_ALLOWANCE_BELOW_ZERO");
_approve(msg.sender, _spender, currentAllowance.sub(_subtractedValue));
return true;
}
/**
* @return the total amount of shares in existence.
*
* @dev The sum of all accounts' shares can be an arbitrary number, therefore
* it is necessary to store it in order to calculate each account's relative share.
*/
function getTotalShares() public view returns (uint256) {
return _getTotalShares();
}
/**
* @return the amount of shares owned by `_account`.
*/
function sharesOf(address _account) public view returns (uint256) {
return _sharesOf(_account);
}
/**
* @return the amount of shares that corresponds to `_ethAmount` protocol-controlled Ether.
*/
function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) {
uint256 totalPooledEther = _getTotalPooledEther();
if (totalPooledEther == 0) {
return 0;
} else {
return _ethAmount
.mul(_getTotalShares())
.div(totalPooledEther);
}
}
/**
* @return the amount of Ether that corresponds to `_sharesAmount` token shares.
*/
function getPooledEthByShares(uint256 _sharesAmount) public view returns (uint256) {
uint256 totalShares = _getTotalShares();
if (totalShares == 0) {
return 0;
} else {
return _sharesAmount
.mul(_getTotalPooledEther())
.div(totalShares);
}
}
/**
* @return the total amount (in wei) of Ether controlled by the protocol.
* @dev This is used for calaulating tokens from shares and vice versa.
* @dev This function is required to be implemented in a derived contract.
*/
function _getTotalPooledEther() internal view returns (uint256);
/**
* @notice Moves `_amount` tokens from `_sender` to `_recipient`.
* Emits a `Transfer` event.
*/
function _transfer(address _sender, address _recipient, uint256 _amount) internal {
uint256 _sharesToTransfer = getSharesByPooledEth(_amount);
_transferShares(_sender, _recipient, _sharesToTransfer);
emit Transfer(_sender, _recipient, _amount);
}
/**
* @notice Sets `_amount` as the allowance of `_spender` over the `_owner` s tokens.
*
* Emits an `Approval` event.
*
* Requirements:
*
* - `_owner` cannot be the zero address.
* - `_spender` cannot be the zero address.
* - the contract must not be paused.
*/
function _approve(address _owner, address _spender, uint256 _amount) internal whenNotStopped {
require(_owner != address(0), "APPROVE_FROM_ZERO_ADDRESS");
require(_spender != address(0), "APPROVE_TO_ZERO_ADDRESS");
allowances[_owner][_spender] = _amount;
emit Approval(_owner, _spender, _amount);
}
/**
* @return the total amount of shares in existence.
*/
function _getTotalShares() internal view returns (uint256) {
return TOTAL_SHARES_POSITION.getStorageUint256();
}
/**
* @return the amount of shares owned by `_account`.
*/
function _sharesOf(address _account) internal view returns (uint256) {
return shares[_account];
}
/**
* @notice Moves `_sharesAmount` shares from `_sender` to `_recipient`.
*
* Requirements:
*
* - `_sender` cannot be the zero address.
* - `_recipient` cannot be the zero address.
* - `_sender` must hold at least `_sharesAmount` shares.
* - the contract must not be paused.
*/
function _transferShares(address _sender, address _recipient, uint256 _sharesAmount) internal whenNotStopped {
require(_sender != address(0), "TRANSFER_FROM_THE_ZERO_ADDRESS");
require(_recipient != address(0), "TRANSFER_TO_THE_ZERO_ADDRESS");
uint256 currentSenderShares = shares[_sender];
require(_sharesAmount <= currentSenderShares, "TRANSFER_AMOUNT_EXCEEDS_BALANCE");
shares[_sender] = currentSenderShares.sub(_sharesAmount);
shares[_recipient] = shares[_recipient].add(_sharesAmount);
}
/**
* @notice Creates `_sharesAmount` shares and assigns them to `_recipient`, increasing the total amount of shares.
* @dev This doesn't increase the token total supply.
*
* Requirements:
*
* - `_recipient` cannot be the zero address.
* - the contract must not be paused.
*/
function _mintShares(address _recipient, uint256 _sharesAmount) internal whenNotStopped returns (uint256 newTotalShares) {
require(_recipient != address(0), "MINT_TO_THE_ZERO_ADDRESS");
newTotalShares = _getTotalShares().add(_sharesAmount);
TOTAL_SHARES_POSITION.setStorageUint256(newTotalShares);
shares[_recipient] = shares[_recipient].add(_sharesAmount);
// Notice: we're not emitting a Transfer event from the zero address here since shares mint
// works by taking the amount of tokens corresponding to the minted shares from all other
// token holders, proportionally to their share. The total supply of the token doesn't change
// as the result. This is equivalent to performing a send from each other token holder's
// address to `address`, but we cannot reflect this as it would require sending an unbounded
// number of events.
}
/**
* @notice Destroys `_sharesAmount` shares from `_account`'s holdings, decreasing the total amount of shares.
* @dev This doesn't decrease the token total supply.
*
* Requirements:
*
* - `_account` cannot be the zero address.
* - `_account` must hold at least `_sharesAmount` shares.
* - the contract must not be paused.
*/
function _burnShares(address _account, uint256 _sharesAmount) internal whenNotStopped returns (uint256 newTotalShares) {
require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS");
uint256 accountShares = shares[_account];
require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE");
newTotalShares = _getTotalShares().sub(_sharesAmount);
TOTAL_SHARES_POSITION.setStorageUint256(newTotalShares);
shares[_account] = accountShares.sub(_sharesAmount);
// Notice: we're not emitting a Transfer event to the zero address here since shares burn
// works by redistributing the amount of tokens corresponding to the burned shares between
// all other token holders. The total supply of the token doesn't change as the result.
// This is equivalent to performing a send from `address` to each other token holder address,
// but we cannot reflect this as it would require sending an unbounded number of events.
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "../common/UnstructuredStorage.sol";
import "../kernel/IKernel.sol";
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);
}
}
/*
* 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));
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "./Petrifiable.sol";
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();
}
}
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)
}
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "../common/UnstructuredStorage.sol";
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);
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "../lib/token/ERC20.sol";
import "./EtherTokenConstant.sol";
import "./IsContract.sol";
import "./IVaultRecoverable.sol";
import "./SafeERC20.sol";
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);
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "./IEVMScriptExecutor.sol";
import "./IEVMScriptRegistry.sol";
import "../apps/AppStorage.sol";
import "../kernel/KernelConstants.sol";
import "../common/Initializable.sol";
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);
}
}
/*
* 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) }
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "../acl/IACL.sol";
import "../common/IVaultRecoverable.sol";
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);
}
/*
* 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);
}
/*
* 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);
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "./Initializable.sol";
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);
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "./TimeHelpers.sol";
import "./UnstructuredStorage.sol";
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);
}
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "./Uint256Helpers.sol";
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();
}
}
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);
}
}
// 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
);
}
/*
* 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);
}
// 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;
import "../lib/token/ERC20.sol";
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;
}
}
/*
* 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);
}
/*
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.4.24;
import "./IEVMScriptExecutor.sol";
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);
}
/*
* 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;
}
pragma solidity ^0.4.24;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender)
external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value)
external returns (bool);
function transferFrom(address from, address to, uint256 value)
external returns (bool);
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
// SPDX-FileCopyrightText: 2020 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.24;
import "@aragon/os/contracts/common/UnstructuredStorage.sol";
contract Pausable {
using UnstructuredStorage for bytes32;
event Stopped();
event Resumed();
bytes32 internal constant ACTIVE_FLAG_POSITION = keccak256("lido.Pausable.activeFlag");
modifier whenNotStopped() {
require(ACTIVE_FLAG_POSITION.getStorageBool(), "CONTRACT_IS_STOPPED");
_;
}
modifier whenStopped() {
require(!ACTIVE_FLAG_POSITION.getStorageBool(), "CONTRACT_IS_ACTIVE");
_;
}
function isStopped() external view returns (bool) {
return !ACTIVE_FLAG_POSITION.getStorageBool();
}
function _stop() internal whenNotStopped {
ACTIVE_FLAG_POSITION.setStorageBool(false);
emit Stopped();
}
function _resume() internal whenStopped {
ACTIVE_FLAG_POSITION.setStorageBool(true);
emit Resumed();
}
}