1253 lines
40 KiB
Solidity
1253 lines
40 KiB
Solidity
|
|
// File: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/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: @aragon/os/contracts/common/Autopetrified.sol
|
|
|
|
/*
|
|
* SPDX-License-Identitifer: MIT
|
|
*/
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
|
|
contract Autopetrified is Petrifiable {
|
|
constructor() public {
|
|
// Immediately petrify base (non-proxy) instances of inherited contracts on deploy.
|
|
// This renders them uninitializable (and unusable without a proxy).
|
|
petrify();
|
|
}
|
|
}
|
|
|
|
// File: @aragon/os/contracts/common/ConversionHelpers.sol
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
library ConversionHelpers {
|
|
string private constant ERROR_IMPROPER_LENGTH = "CONVERSION_IMPROPER_LENGTH";
|
|
|
|
function dangerouslyCastUintArrayToBytes(uint256[] memory _input) internal pure returns (bytes memory output) {
|
|
// Force cast the uint256[] into a bytes array, by overwriting its length
|
|
// Note that the bytes array doesn't need to be initialized as we immediately overwrite it
|
|
// with the input and a new length. The input becomes invalid from this point forward.
|
|
uint256 byteLength = _input.length * 32;
|
|
assembly {
|
|
output := _input
|
|
mstore(output, byteLength)
|
|
}
|
|
}
|
|
|
|
function dangerouslyCastBytesToUintArray(bytes memory _input) internal pure returns (uint256[] memory output) {
|
|
// Force cast the bytes array into a uint256[], by overwriting its length
|
|
// Note that the uint256[] doesn't need to be initialized as we immediately overwrite it
|
|
// with the input and a new length. The input becomes invalid from this point forward.
|
|
uint256 intsLength = _input.length / 32;
|
|
require(_input.length == intsLength * 32, ERROR_IMPROPER_LENGTH);
|
|
|
|
assembly {
|
|
output := _input
|
|
mstore(output, intsLength)
|
|
}
|
|
}
|
|
}
|
|
|
|
// File: @aragon/os/contracts/common/ReentrancyGuard.sol
|
|
|
|
/*
|
|
* SPDX-License-Identitifer: MIT
|
|
*/
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
|
|
contract ReentrancyGuard {
|
|
using UnstructuredStorage for bytes32;
|
|
|
|
/* Hardcoded constants to save gas
|
|
bytes32 internal constant REENTRANCY_MUTEX_POSITION = keccak256("aragonOS.reentrancyGuard.mutex");
|
|
*/
|
|
bytes32 private constant REENTRANCY_MUTEX_POSITION = 0xe855346402235fdd185c890e68d2c4ecad599b88587635ee285bce2fda58dacb;
|
|
|
|
string private constant ERROR_REENTRANT = "REENTRANCY_REENTRANT_CALL";
|
|
|
|
modifier nonReentrant() {
|
|
// Ensure mutex is unlocked
|
|
require(!REENTRANCY_MUTEX_POSITION.getStorageBool(), ERROR_REENTRANT);
|
|
|
|
// Lock mutex before function call
|
|
REENTRANCY_MUTEX_POSITION.setStorageBool(true);
|
|
|
|
// Perform function call
|
|
_;
|
|
|
|
// Unlock mutex after function call
|
|
REENTRANCY_MUTEX_POSITION.setStorageBool(false);
|
|
}
|
|
}
|
|
|
|
// File: @aragon/os/contracts/lib/token/ERC20.sol
|
|
|
|
// See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/a9f910d34f0ab33a1ae5e714f69f9596a02b4d91/contracts/token/ERC20/ERC20.sol
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
/**
|
|
* @title ERC20 interface
|
|
* @dev see https://github.com/ethereum/EIPs/issues/20
|
|
*/
|
|
contract ERC20 {
|
|
function totalSupply() public view returns (uint256);
|
|
|
|
function balanceOf(address _who) public view returns (uint256);
|
|
|
|
function allowance(address _owner, address _spender)
|
|
public view returns (uint256);
|
|
|
|
function transfer(address _to, uint256 _value) public returns (bool);
|
|
|
|
function approve(address _spender, uint256 _value)
|
|
public returns (bool);
|
|
|
|
function transferFrom(address _from, address _to, uint256 _value)
|
|
public returns (bool);
|
|
|
|
event Transfer(
|
|
address indexed from,
|
|
address indexed to,
|
|
uint256 value
|
|
);
|
|
|
|
event Approval(
|
|
address indexed owner,
|
|
address indexed spender,
|
|
uint256 value
|
|
);
|
|
}
|
|
|
|
// File: @aragon/os/contracts/common/EtherTokenConstant.sol
|
|
|
|
/*
|
|
* 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: @aragon/os/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: @aragon/os/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;
|
|
}
|
|
}
|
|
|
|
// File: @aragon/os/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: @aragon/os/contracts/evmscript/IEVMScriptExecutor.sol
|
|
|
|
/*
|
|
* SPDX-License-Identitifer: MIT
|
|
*/
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
interface IEVMScriptExecutor {
|
|
function execScript(bytes script, bytes input, address[] blacklist) external returns (bytes);
|
|
function executorType() external pure returns (bytes32);
|
|
}
|
|
|
|
// File: @aragon/os/contracts/evmscript/IEVMScriptRegistry.sol
|
|
|
|
/*
|
|
* SPDX-License-Identitifer: MIT
|
|
*/
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
|
|
contract EVMScriptRegistryConstants {
|
|
/* Hardcoded constants to save gas
|
|
bytes32 internal constant EVMSCRIPT_REGISTRY_APP_ID = apmNamehash("evmreg");
|
|
*/
|
|
bytes32 internal constant EVMSCRIPT_REGISTRY_APP_ID = 0xddbcfd564f642ab5627cf68b9b7d374fb4f8a36e941a75d89c87998cef03bd61;
|
|
}
|
|
|
|
|
|
interface IEVMScriptRegistry {
|
|
function addScriptExecutor(IEVMScriptExecutor executor) external returns (uint id);
|
|
function disableScriptExecutor(uint256 executorId) external;
|
|
|
|
// TODO: this should be external
|
|
// See https://github.com/ethereum/solidity/issues/4832
|
|
function getScriptExecutor(bytes script) public view returns (IEVMScriptExecutor);
|
|
}
|
|
|
|
// File: @aragon/os/contracts/kernel/KernelConstants.sol
|
|
|
|
/*
|
|
* 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: @aragon/os/contracts/evmscript/EVMScriptRunner.sol
|
|
|
|
/*
|
|
* SPDX-License-Identitifer: MIT
|
|
*/
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
contract EVMScriptRunner is AppStorage, Initializable, EVMScriptRegistryConstants, KernelNamespaceConstants {
|
|
string private constant ERROR_EXECUTOR_UNAVAILABLE = "EVMRUN_EXECUTOR_UNAVAILABLE";
|
|
string private constant ERROR_PROTECTED_STATE_MODIFIED = "EVMRUN_PROTECTED_STATE_MODIFIED";
|
|
|
|
/* This is manually crafted in assembly
|
|
string private constant ERROR_EXECUTOR_INVALID_RETURN = "EVMRUN_EXECUTOR_INVALID_RETURN";
|
|
*/
|
|
|
|
event ScriptResult(address indexed executor, bytes script, bytes input, bytes returnData);
|
|
|
|
function getEVMScriptExecutor(bytes _script) public view returns (IEVMScriptExecutor) {
|
|
return IEVMScriptExecutor(getEVMScriptRegistry().getScriptExecutor(_script));
|
|
}
|
|
|
|
function getEVMScriptRegistry() public view returns (IEVMScriptRegistry) {
|
|
address registryAddr = kernel().getApp(KERNEL_APP_ADDR_NAMESPACE, EVMSCRIPT_REGISTRY_APP_ID);
|
|
return IEVMScriptRegistry(registryAddr);
|
|
}
|
|
|
|
function runScript(bytes _script, bytes _input, address[] _blacklist)
|
|
internal
|
|
isInitialized
|
|
protectState
|
|
returns (bytes)
|
|
{
|
|
IEVMScriptExecutor executor = getEVMScriptExecutor(_script);
|
|
require(address(executor) != address(0), ERROR_EXECUTOR_UNAVAILABLE);
|
|
|
|
bytes4 sig = executor.execScript.selector;
|
|
bytes memory data = abi.encodeWithSelector(sig, _script, _input, _blacklist);
|
|
|
|
bytes memory output;
|
|
assembly {
|
|
let success := delegatecall(
|
|
gas, // forward all gas
|
|
executor, // address
|
|
add(data, 0x20), // calldata start
|
|
mload(data), // calldata length
|
|
0, // don't write output (we'll handle this ourselves)
|
|
0 // don't write output
|
|
)
|
|
|
|
output := mload(0x40) // free mem ptr get
|
|
|
|
switch success
|
|
case 0 {
|
|
// If the call errored, forward its full error data
|
|
returndatacopy(output, 0, returndatasize)
|
|
revert(output, returndatasize)
|
|
}
|
|
default {
|
|
switch gt(returndatasize, 0x3f)
|
|
case 0 {
|
|
// Need at least 0x40 bytes returned for properly ABI-encoded bytes values,
|
|
// revert with "EVMRUN_EXECUTOR_INVALID_RETURN"
|
|
// See remix: doing a `revert("EVMRUN_EXECUTOR_INVALID_RETURN")` always results in
|
|
// this memory layout
|
|
mstore(output, 0x08c379a000000000000000000000000000000000000000000000000000000000) // error identifier
|
|
mstore(add(output, 0x04), 0x0000000000000000000000000000000000000000000000000000000000000020) // starting offset
|
|
mstore(add(output, 0x24), 0x000000000000000000000000000000000000000000000000000000000000001e) // reason length
|
|
mstore(add(output, 0x44), 0x45564d52554e5f4558454355544f525f494e56414c49445f52455455524e0000) // reason
|
|
|
|
revert(output, 100) // 100 = 4 + 3 * 32 (error identifier + 3 words for the ABI encoded error)
|
|
}
|
|
default {
|
|
// Copy result
|
|
//
|
|
// Needs to perform an ABI decode for the expected `bytes` return type of
|
|
// `executor.execScript()` as solidity will automatically ABI encode the returned bytes as:
|
|
// [ position of the first dynamic length return value = 0x20 (32 bytes) ]
|
|
// [ output length (32 bytes) ]
|
|
// [ output content (N bytes) ]
|
|
//
|
|
// Perform the ABI decode by ignoring the first 32 bytes of the return data
|
|
let copysize := sub(returndatasize, 0x20)
|
|
returndatacopy(output, 0x20, copysize)
|
|
|
|
mstore(0x40, add(output, copysize)) // free mem ptr set
|
|
}
|
|
}
|
|
}
|
|
|
|
emit ScriptResult(address(executor), _script, _input, output);
|
|
|
|
return output;
|
|
}
|
|
|
|
modifier protectState {
|
|
address preKernel = address(kernel());
|
|
bytes32 preAppId = appId();
|
|
_; // exec
|
|
require(address(kernel()) == preKernel, ERROR_PROTECTED_STATE_MODIFIED);
|
|
require(appId() == preAppId, ERROR_PROTECTED_STATE_MODIFIED);
|
|
}
|
|
}
|
|
|
|
// File: @aragon/os/contracts/apps/AragonApp.sol
|
|
|
|
/*
|
|
* SPDX-License-Identitifer: 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/Contribution.sol
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
|
|
interface IToken {
|
|
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public;
|
|
}
|
|
|
|
interface ContributorInterface {
|
|
function getContributorAddressById(uint32 contributorId) public view returns (address);
|
|
function getContributorIdByAddress(address contributorAccount) public view returns (uint32);
|
|
// TODO Maybe use for validation
|
|
// function exists(uint32 contributorId) public view returns (bool);
|
|
}
|
|
|
|
contract Contribution is AragonApp {
|
|
bytes32 public constant ADD_CONTRIBUTION_ROLE = keccak256("ADD_CONTRIBUTION_ROLE");
|
|
bytes32 public constant VETO_CONTRIBUTION_ROLE = keccak256("VETO_CONTRIBUTION_ROLE");
|
|
|
|
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
|
|
|
// ensure alphabetic order
|
|
enum Apps { Contribution, Contributor, Proposal, Token }
|
|
bytes32[4] public appIds;
|
|
|
|
struct ContributionData {
|
|
uint32 contributorId;
|
|
uint32 amount;
|
|
bool claimed;
|
|
bytes32 hashDigest;
|
|
uint8 hashFunction;
|
|
uint8 hashSize;
|
|
string tokenMetadataURL;
|
|
uint256 confirmedAtBlock;
|
|
bool vetoed;
|
|
bool exists;
|
|
}
|
|
|
|
string internal name_;
|
|
string internal symbol_;
|
|
|
|
// map contribution ID to contributor
|
|
mapping(uint32 => uint32) public contributionOwner;
|
|
// map contributor to contribution IDs
|
|
mapping(uint32 => uint32[]) public ownedContributions;
|
|
|
|
mapping(uint32 => ContributionData) public contributions;
|
|
uint32 public contributionsCount;
|
|
|
|
uint32 public blocksToWait;
|
|
|
|
event ContributionAdded(uint32 id, uint32 indexed contributorId, uint32 amount);
|
|
event ContributionClaimed(uint32 id, uint32 indexed contributorId, uint32 amount);
|
|
event ContributionVetoed(uint32 id, address vetoedByAccount);
|
|
|
|
function initialize(bytes32[4] _appIds) public onlyInit {
|
|
appIds = _appIds;
|
|
blocksToWait = 40320; // 7 days; 15 seconds block time
|
|
initialized();
|
|
}
|
|
|
|
// TODO refactor into a single function
|
|
function getTokenContract() public view returns (address) {
|
|
IKernel k = IKernel(kernel());
|
|
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Token)]);
|
|
}
|
|
function getContributorContract() public view returns (address) {
|
|
IKernel k = IKernel(kernel());
|
|
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Contributor)]);
|
|
}
|
|
|
|
function getContributorIdByAddress(address contributorAccount) public view returns (uint32) {
|
|
address contributor = getContributorContract();
|
|
return ContributorInterface(contributor).getContributorIdByAddress(contributorAccount);
|
|
}
|
|
|
|
function getContributorAddressById(uint32 contributorId) public view returns (address) {
|
|
address contributor = getContributorContract();
|
|
return ContributorInterface(contributor).getContributorAddressById(contributorId);
|
|
}
|
|
|
|
//
|
|
// Token standard functions (ERC 721)
|
|
//
|
|
|
|
function name() external view returns (string) {
|
|
return name_;
|
|
}
|
|
|
|
function symbol() external view returns (string) {
|
|
return symbol_;
|
|
}
|
|
|
|
// Balance is amount of ERC271 tokens, not amount of kredits
|
|
function balanceOf(address owner) public view returns (uint256) {
|
|
require(owner != address(0));
|
|
uint32 contributorId = getContributorIdByAddress(owner);
|
|
return ownedContributions[contributorId].length;
|
|
}
|
|
|
|
function ownerOf(uint32 contributionId) public view returns (address) {
|
|
require(exists(contributionId));
|
|
uint32 contributorId = contributions[contributionId].contributorId;
|
|
return getContributorAddressById(contributorId);
|
|
}
|
|
|
|
function tokenOfOwnerByIndex(address owner, uint32 index) public view returns (uint32) {
|
|
uint32 contributorId = getContributorIdByAddress(owner);
|
|
return ownedContributions[contributorId][index];
|
|
}
|
|
|
|
function tokenMetadata(uint32 contributionId) public view returns (string) {
|
|
return contributions[contributionId].tokenMetadataURL;
|
|
}
|
|
|
|
//
|
|
// Custom functions
|
|
//
|
|
|
|
function totalKreditsEarned(bool confirmedOnly) public view returns (uint32 amount) {
|
|
for (uint32 i = 1; i <= contributionsCount; i++) {
|
|
ContributionData memory c = contributions[i];
|
|
if (!c.vetoed && (block.number >= c.confirmedAtBlock || !confirmedOnly)) {
|
|
amount += c.amount; // should use safemath
|
|
}
|
|
}
|
|
}
|
|
|
|
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) public view returns (uint32 amount) {
|
|
uint256 tokenCount = ownedContributions[contributorId].length;
|
|
for (uint256 i = 0; i < tokenCount; i++) {
|
|
uint32 cId = ownedContributions[contributorId][i];
|
|
ContributionData memory c = contributions[cId];
|
|
if (!c.vetoed && (block.number >= c.confirmedAtBlock || !confirmedOnly)) {
|
|
amount += c.amount; // should use safemath
|
|
}
|
|
}
|
|
}
|
|
|
|
function getContribution(uint32 contributionId) public view returns (uint32 id, uint32 contributorId, uint32 amount, bool claimed, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool exists, bool vetoed) {
|
|
id = contributionId;
|
|
ContributionData storage c = contributions[id];
|
|
return (
|
|
id,
|
|
c.contributorId,
|
|
c.amount,
|
|
c.claimed,
|
|
c.hashDigest,
|
|
c.hashFunction,
|
|
c.hashSize,
|
|
c.confirmedAtBlock,
|
|
c.exists,
|
|
c.vetoed
|
|
);
|
|
}
|
|
|
|
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_CONTRIBUTION_ROLE) {
|
|
//require(canPerform(msg.sender, ADD_CONTRIBUTION_ROLE, new uint32[](0)), 'nope');
|
|
uint32 contributionId = contributionsCount + 1;
|
|
ContributionData storage c = contributions[contributionId];
|
|
c.exists = true;
|
|
c.amount = amount;
|
|
c.claimed = false;
|
|
c.contributorId = contributorId;
|
|
c.hashDigest = hashDigest;
|
|
c.hashFunction = hashFunction;
|
|
c.hashSize = hashSize;
|
|
if (contributionId < 10) {
|
|
c.confirmedAtBlock = block.number;
|
|
} else {
|
|
c.confirmedAtBlock = block.number + blocksToWait;
|
|
}
|
|
|
|
contributionsCount++;
|
|
|
|
contributionOwner[contributionId] = contributorId;
|
|
ownedContributions[contributorId].push(contributionId);
|
|
|
|
emit ContributionAdded(contributionId, contributorId, amount);
|
|
}
|
|
|
|
function veto(uint32 contributionId) public isInitialized auth(VETO_CONTRIBUTION_ROLE) {
|
|
ContributionData storage c = contributions[contributionId];
|
|
require(c.exists, 'NOT_FOUND');
|
|
require(!c.claimed, 'ALREADY_CLAIMED');
|
|
require(block.number < c.confirmedAtBlock, 'VETO_PERIOD_ENDED');
|
|
c.vetoed = true;
|
|
|
|
emit ContributionVetoed(contributionId, msg.sender);
|
|
}
|
|
|
|
function claim(uint32 contributionId) public isInitialized {
|
|
ContributionData storage c = contributions[contributionId];
|
|
require(c.exists, 'NOT_FOUND');
|
|
require(!c.claimed, 'ALREADY_CLAIMED');
|
|
require(!c.vetoed, 'VETOED');
|
|
require(block.number >= c.confirmedAtBlock, 'NOT_CLAIMABLE');
|
|
|
|
c.claimed = true;
|
|
address token = getTokenContract();
|
|
address contributorAccount = getContributorAddressById(c.contributorId);
|
|
uint256 amount = uint256(c.amount);
|
|
IToken(token).mintFor(contributorAccount, amount, contributionId);
|
|
emit ContributionClaimed(contributionId, c.contributorId, c.amount);
|
|
}
|
|
|
|
function exists(uint32 contributionId) view public returns (bool) {
|
|
return contributions[contributionId].exists;
|
|
}
|
|
}
|