///File: ./contracts/ILiquidPledgingPlugin.sol pragma solidity ^0.4.0; /* Copyright 2018, Jordi Baylina Contributors: Adrià Massanet , RJ Ewing, Griff Green, Arthur Lunn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /// @dev `ILiquidPledgingPlugin` is the basic interface for any /// liquid pledging plugin contract ILiquidPledgingPlugin { /// @notice Plugins are used (much like web hooks) to initiate an action /// upon any donation, delegation, or transfer; this is an optional feature /// and allows for extreme customization of the contract. This function /// implements any action that should be initiated before a transfer. /// @param pledgeManager The admin or current manager of the pledge /// @param pledgeFrom This is the Id from which value will be transfered. /// @param pledgeTo This is the Id that value will be transfered to. /// @param context The situation that is triggering the plugin: /// 0 -> Plugin for the owner transferring pledge to another party /// 1 -> Plugin for the first delegate transferring pledge to another party /// 2 -> Plugin for the second delegate transferring pledge to another party /// ... /// 255 -> Plugin for the intendedProject transferring pledge to another party /// /// 256 -> Plugin for the owner receiving pledge to another party /// 257 -> Plugin for the first delegate receiving pledge to another party /// 258 -> Plugin for the second delegate receiving pledge to another party /// ... /// 511 -> Plugin for the intendedProject receiving pledge to another party /// @param amount The amount of value that will be transfered. function beforeTransfer( uint64 pledgeManager, uint64 pledgeFrom, uint64 pledgeTo, uint64 context, address token, uint amount ) public returns (uint maxAllowed); /// @notice Plugins are used (much like web hooks) to initiate an action /// upon any donation, delegation, or transfer; this is an optional feature /// and allows for extreme customization of the contract. This function /// implements any action that should be initiated after a transfer. /// @param pledgeManager The admin or current manager of the pledge /// @param pledgeFrom This is the Id from which value will be transfered. /// @param pledgeTo This is the Id that value will be transfered to. /// @param context The situation that is triggering the plugin: /// 0 -> Plugin for the owner transferring pledge to another party /// 1 -> Plugin for the first delegate transferring pledge to another party /// 2 -> Plugin for the second delegate transferring pledge to another party /// ... /// 255 -> Plugin for the intendedProject transferring pledge to another party /// /// 256 -> Plugin for the owner receiving pledge to another party /// 257 -> Plugin for the first delegate receiving pledge to another party /// 258 -> Plugin for the second delegate receiving pledge to another party /// ... /// 511 -> Plugin for the intendedProject receiving pledge to another party /// @param amount The amount of value that will be transfered. function afterTransfer( uint64 pledgeManager, uint64 pledgeFrom, uint64 pledgeTo, uint64 context, address token, uint amount ) public; } ///File: ./contracts/LiquidPledgingStorage.sol pragma solidity ^0.4.18; /// @dev This is an interface for `LPVault` which serves as a secure storage for /// the ETH that backs the Pledges, only after `LiquidPledging` authorizes /// payments can Pledges be converted for ETH interface ILPVault { function authorizePayment(bytes32 _ref, address _dest, address _token, uint _amount) public; } /// This contract contains all state variables used in LiquidPledging contracts /// This is done to have everything in 1 location, b/c state variable layout /// is MUST have be the same when performing an upgrade. contract LiquidPledgingStorage { enum PledgeAdminType { Giver, Delegate, Project } enum PledgeState { Pledged, Paying, Paid } /// @dev This struct defines the details of a `PledgeAdmin` which are /// commonly referenced by their index in the `admins` array /// and can own pledges and act as delegates struct PledgeAdmin { PledgeAdminType adminType; // Giver, Delegate or Project address addr; // Account or contract address for admin uint64 commitTime; // In seconds, used for time Givers' & Delegates' have to veto uint64 parentProject; // Only for projects bool canceled; //Always false except for canceled projects /// @dev if the plugin is 0x0 then nothing happens, if its an address // than that smart contract is called when appropriate ILiquidPledgingPlugin plugin; string name; string url; // Can be IPFS hash } struct Pledge { uint amount; uint64[] delegationChain; // List of delegates in order of authority uint64 owner; // PledgeAdmin uint64 intendedProject; // Used when delegates are sending to projects uint64 commitTime; // When the intendedProject will become the owner uint64 oldPledge; // Points to the id that this Pledge was derived from address token; PledgeState pledgeState; // Pledged, Paying, Paid } PledgeAdmin[] admins; //The list of pledgeAdmins 0 means there is no admin Pledge[] pledges; /// @dev this mapping allows you to search for a specific pledge's /// index number by the hash of that pledge mapping (bytes32 => uint64) hPledge2idx; // this whitelist is for non-proxied plugins mapping (bytes32 => bool) pluginContractWhitelist; // this whitelist is for proxied plugins mapping (address => bool) pluginInstanceWhitelist; bool public whitelistDisabled = false; ILPVault public vault; // reserve 50 slots for future upgrades. uint[50] private storageOffset; } ///File: @aragon/os/contracts/acl/IACL.sol pragma solidity ^0.4.18; interface IACL { function initialize(address permissionsCreator) public; function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); } ///File: @aragon/os/contracts/common/IVaultRecoverable.sol pragma solidity ^0.4.18; interface IVaultRecoverable { function transferToVault(address token) external; function allowRecoverability(address token) public view returns (bool); function getRecoveryVault() public view returns (address); } ///File: @aragon/os/contracts/kernel/IKernel.sol pragma solidity ^0.4.18; // This should be an interface, but interfaces can't inherit yet :( contract IKernel is IVaultRecoverable { event SetApp(bytes32 indexed namespace, bytes32 indexed name, bytes32 indexed id, address app); 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 name, address app) public returns (bytes32 id); function getApp(bytes32 id) public view returns (address); } ///File: @aragon/os/contracts/apps/AppStorage.sol pragma solidity ^0.4.18; contract AppStorage { IKernel public kernel; bytes32 public appId; address internal pinnedCode; // used by Proxy Pinned uint256 internal initializationBlock; // used by Initializable uint256[95] private storageOffset; // forces App storage to start at after 100 slots uint256 private offset; } ///File: @aragon/os/contracts/common/Initializable.sol pragma solidity ^0.4.18; contract Initializable is AppStorage { modifier onlyInit { require(initializationBlock == 0); _; } modifier isInitialized { require(initializationBlock > 0); _; } /** * @return Block number in which the contract was initialized */ function getInitializationBlock() public view returns (uint256) { return initializationBlock; } /** * @dev Function to be called by top level contract after initialization has finished. */ function initialized() internal onlyInit { initializationBlock = getBlockNumber(); } /** * @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; } } ///File: @aragon/os/contracts/common/EtherTokenConstant.sol pragma solidity ^0.4.18; // aragonOS and aragon-apps rely on address(0) to denote native ETH, in // contracts where both tokens and ETH are accepted contract EtherTokenConstant { address constant public ETH = address(0); } ///File: @aragon/os/contracts/common/IsContract.sol pragma solidity ^0.4.18; contract IsContract { 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/lib/zeppelin/token/ERC20Basic.sol pragma solidity ^0.4.11; /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/179 */ contract ERC20Basic { function totalSupply() public view returns (uint256); function balanceOf(address who) public constant returns (uint256); function transfer(address to, uint256 value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } ///File: @aragon/os/contracts/lib/zeppelin/token/ERC20.sol pragma solidity ^0.4.11; import './ERC20Basic.sol'; /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public constant returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval(address indexed owner, address indexed spender, uint256 value); } ///File: @aragon/os/contracts/common/VaultRecoverable.sol pragma solidity ^0.4.18; contract VaultRecoverable is IVaultRecoverable, EtherTokenConstant, IsContract { /** * @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)); address vault = getRecoveryVault(); require(isContract(vault)); if (_token == ETH) { vault.transfer(this.balance); } else { uint256 amount = ERC20(_token).balanceOf(this); ERC20(_token).transfer(vault, amount); } } /** * @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; } } ///File: @aragon/os/contracts/evmscript/ScriptHelpers.sol pragma solidity ^0.4.18; library ScriptHelpers { // To test with JS and compare with actual encoder. Maintaining for reference. // t = function() { return IEVMScriptExecutor.at('0x4bcdd59d6c77774ee7317fc1095f69ec84421e49').contract.execScript.getData(...[].slice.call(arguments)).slice(10).match(/.{1,64}/g) } // run = function() { return ScriptHelpers.new().then(sh => { sh.abiEncode.call(...[].slice.call(arguments)).then(a => console.log(a.slice(2).match(/.{1,64}/g)) ) }) } // This is truly not beautiful but lets no daydream to the day solidity gets reflection features function abiEncode(bytes _a, bytes _b, address[] _c) public pure returns (bytes d) { return encode(_a, _b, _c); } function encode(bytes memory _a, bytes memory _b, address[] memory _c) internal pure returns (bytes memory d) { // A is positioned after the 3 position words uint256 aPosition = 0x60; uint256 bPosition = aPosition + 32 * abiLength(_a); uint256 cPosition = bPosition + 32 * abiLength(_b); uint256 length = cPosition + 32 * abiLength(_c); d = new bytes(length); assembly { // Store positions mstore(add(d, 0x20), aPosition) mstore(add(d, 0x40), bPosition) mstore(add(d, 0x60), cPosition) } // Copy memory to correct position copy(d, getPtr(_a), aPosition, _a.length); copy(d, getPtr(_b), bPosition, _b.length); copy(d, getPtr(_c), cPosition, _c.length * 32); // 1 word per address } function abiLength(bytes memory _a) internal pure returns (uint256) { // 1 for length + // memory words + 1 if not divisible for 32 to offset word return 1 + (_a.length / 32) + (_a.length % 32 > 0 ? 1 : 0); } function abiLength(address[] _a) internal pure returns (uint256) { // 1 for length + 1 per item return 1 + _a.length; } function copy(bytes _d, uint256 _src, uint256 _pos, uint256 _length) internal pure { uint dest; assembly { dest := add(add(_d, 0x20), _pos) } memcpy(dest, _src, _length + 32); } function getPtr(bytes memory _x) internal pure returns (uint256 ptr) { assembly { ptr := _x } } function getPtr(address[] memory _x) internal pure returns (uint256 ptr) { assembly { ptr := _x } } 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; } function memcpy(uint _dest, uint _src, uint _len) internal pure { uint256 src = _src; uint256 dest = _dest; uint256 len = _len; // Copy word-length chunks while possible for (; len >= 32; len -= 32) { assembly { mstore(dest, mload(src)) } dest += 32; src += 32; } // Copy remaining bytes uint mask = 256 ** (32 - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) let destpart := and(mload(dest), mask) mstore(dest, or(destpart, srcpart)) } } } ///File: @aragon/os/contracts/evmscript/IEVMScriptExecutor.sol pragma solidity ^0.4.18; interface IEVMScriptExecutor { function execScript(bytes script, bytes input, address[] blacklist) external returns (bytes); } ///File: @aragon/os/contracts/evmscript/IEVMScriptRegistry.sol pragma solidity ^0.4.18; contract EVMScriptRegistryConstants { /* Hardcoded constants to save gas // repeated definitions from KernelStorage, to avoid out of gas issues bytes32 constant public APP_ADDR_NAMESPACE = keccak256("app"); bytes32 constant public EVMSCRIPT_REGISTRY_APP_ID = apmNamehash("evmreg"); bytes32 constant public EVMSCRIPT_REGISTRY_APP = keccak256(APP_ADDR_NAMESPACE, EVMSCRIPT_REGISTRY_APP_ID); */ bytes32 constant public APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb; bytes32 constant public EVMSCRIPT_REGISTRY_APP_ID = 0xddbcfd564f642ab5627cf68b9b7d374fb4f8a36e941a75d89c87998cef03bd61; bytes32 constant public EVMSCRIPT_REGISTRY_APP = 0x34f01c17e9be6ddbf2c61f37b5b1fb9f1a090a975006581ad19bda1c4d382871; } interface IEVMScriptRegistry { function addScriptExecutor(address executor) external returns (uint id); function disableScriptExecutor(uint256 executorId) external; function getScriptExecutor(bytes script) public view returns (address); } ///File: @aragon/os/contracts/evmscript/EVMScriptRunner.sol pragma solidity ^0.4.18; contract EVMScriptRunner is AppStorage, EVMScriptRegistryConstants { using ScriptHelpers for bytes; function runScript(bytes _script, bytes _input, address[] _blacklist) protectState internal returns (bytes output) { // TODO: Too much data flying around, maybe extracting spec id here is cheaper address executorAddr = getExecutor(_script); require(executorAddr != address(0)); bytes memory calldataArgs = _script.encode(_input, _blacklist); bytes4 sig = IEVMScriptExecutor(0).execScript.selector; require(executorAddr.delegatecall(sig, calldataArgs)); bytes memory ret = returnedDataDecoded(); require(ret.length > 0); return ret; } function getExecutor(bytes _script) public view returns (IEVMScriptExecutor) { return IEVMScriptExecutor(getExecutorRegistry().getScriptExecutor(_script)); } // TODO: Internal function getExecutorRegistry() internal view returns (IEVMScriptRegistry) { address registryAddr = kernel.getApp(EVMSCRIPT_REGISTRY_APP); return IEVMScriptRegistry(registryAddr); } /** * @dev copies and returns last's call data. Needs to ABI decode first */ function returnedDataDecoded() internal pure returns (bytes ret) { assembly { let size := returndatasize switch size case 0 {} default { ret := mload(0x40) // free mem ptr get mstore(0x40, add(ret, add(size, 0x20))) // free mem ptr set returndatacopy(ret, 0x20, sub(size, 0x20)) // copy return data } } return ret; } modifier protectState { address preKernel = kernel; bytes32 preAppId = appId; _; // exec require(kernel == preKernel); require(appId == preAppId); } } ///File: @aragon/os/contracts/acl/ACLSyntaxSugar.sol pragma solidity ^0.4.18; contract ACLSyntaxSugar { function arr() internal pure returns (uint256[] r) {} 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) 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/apps/AragonApp.sol pragma solidity ^0.4.18; // ACLSyntaxSugar and EVMScriptRunner are not directly used by this contract, but are included so // that they are automatically usable by subclassing contracts contract AragonApp is AppStorage, Initializable, ACLSyntaxSugar, VaultRecoverable, EVMScriptRunner { modifier auth(bytes32 _role) { require(canPerform(msg.sender, _role, new uint256[](0))); _; } modifier authP(bytes32 _role, uint256[] params) { require(canPerform(msg.sender, _role, params)); _; } function canPerform(address _sender, bytes32 _role, uint256[] params) public view returns (bool) { bytes memory how; // no need to init memory as it is never used if (params.length > 0) { uint256 byteLength = params.length * 32; assembly { how := params // forced casting mstore(how, byteLength) } } return address(kernel) == 0 || kernel.hasPermission(_sender, address(this), _role, how); } function getRecoveryVault() public view returns (address) { // Funds recovery via a vault is only available when used with a kernel require(address(kernel) != 0); return kernel.getRecoveryVault(); } } ///File: ./contracts/LiquidPledgingACLHelpers.sol pragma solidity ^0.4.18; contract LiquidPledgingACLHelpers { function arr(uint64 a, uint64 b, address c, uint d, address e) internal pure returns(uint[] r) { r = new uint[](4); r[0] = uint(a); r[1] = uint(b); r[2] = uint(c); r[3] = d; r[4] = uint(e); } function arr(bool a) internal pure returns (uint[] r) { r = new uint[](1); uint _a; assembly { _a := a // forced casting } r[0] = _a; } } ///File: ./contracts/LiquidPledgingPlugins.sol pragma solidity ^0.4.18; /* Copyright 2017, Jordi Baylina, RJ Ewing Contributors: Adrià Massanet , Griff Green, Arthur Lunn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ contract LiquidPledgingPlugins is AragonApp, LiquidPledgingStorage, LiquidPledgingACLHelpers { bytes32 constant public PLUGIN_MANAGER_ROLE = keccak256("PLUGIN_MANAGER_ROLE"); /** * @dev adds an instance of a plugin to the whitelist */ function addValidPluginInstance(address addr) auth(PLUGIN_MANAGER_ROLE) external { pluginInstanceWhitelist[addr] = true; } /** * @dev add a contract to the plugin whitelist. * @notice Proxy contracts should never be added using this method. Each individual * proxy instance should be added by calling `addValidPluginInstance` */ function addValidPluginContract(bytes32 contractHash) auth(PLUGIN_MANAGER_ROLE) public { pluginContractWhitelist[contractHash] = true; } function addValidPluginContracts(bytes32[] contractHashes) external auth(PLUGIN_MANAGER_ROLE) { for (uint8 i = 0; i < contractHashes.length; i++) { addValidPluginContract(contractHashes[i]); } } /** * @dev removes a contract from the plugin whitelist */ function removeValidPluginContract(bytes32 contractHash) external authP(PLUGIN_MANAGER_ROLE, arr(contractHash)) { pluginContractWhitelist[contractHash] = false; } /** * @dev removes an instance of a plugin to the whitelist */ function removeValidPluginInstance(address addr) external authP(PLUGIN_MANAGER_ROLE, arr(addr)) { pluginInstanceWhitelist[addr] = false; } /** * @dev enable/disable the plugin whitelist. * @notice you better know what you're doing if you are going to disable it */ function useWhitelist(bool useWhitelist) external auth(PLUGIN_MANAGER_ROLE) { whitelistDisabled = !useWhitelist; } /** * check if the contract at the provided address is in the plugin whitelist */ function isValidPlugin(address addr) public view returns(bool) { if (whitelistDisabled || addr == 0x0) { return true; } // first check pluginInstances if (pluginInstanceWhitelist[addr]) { return true; } // if the addr isn't a valid instance, check the contract code bytes32 contractHash = getCodeHash(addr); return pluginContractWhitelist[contractHash]; } /** * @return the hash of the code for the given address */ function getCodeHash(address addr) public view returns(bytes32) { bytes memory o_code; assembly { // retrieve the size of the code let size := extcodesize(addr) // allocate output byte array o_code := mload(0x40) mstore(o_code, size) // store length in memory // actually retrieve the code extcodecopy(addr, add(o_code, 0x20), 0, size) } return keccak256(o_code); } } ///File: ./contracts/PledgeAdmins.sol pragma solidity ^0.4.18; /* Copyright 2017, Jordi Baylina, RJ Ewing Contributors: Adrià Massanet , Griff Green, Arthur Lunn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ contract PledgeAdmins is AragonApp, LiquidPledgingPlugins { // Limits inserted to prevent large loops that could prevent canceling uint constant MAX_SUBPROJECT_LEVEL = 20; uint constant MAX_INTERPROJECT_LEVEL = 20; // Events event GiverAdded(uint64 indexed idGiver, string url); event GiverUpdated(uint64 indexed idGiver, string url); event DelegateAdded(uint64 indexed idDelegate, string url); event DelegateUpdated(uint64 indexed idDelegate, string url); event ProjectAdded(uint64 indexed idProject, string url); event ProjectUpdated(uint64 indexed idProject, string url); //////////////////// // Public functions //////////////////// /// @notice Creates a Giver Admin with the `msg.sender` as the Admin address /// @param name The name used to identify the Giver /// @param url The link to the Giver's profile often an IPFS hash /// @param commitTime The length of time in seconds the Giver has to /// veto when the Giver's delegates Pledge funds to a project /// @param plugin This is Giver's liquid pledge plugin allowing for /// extended functionality /// @return idGiver The id number used to reference this Admin function addGiver( string name, string url, uint64 commitTime, ILiquidPledgingPlugin plugin ) external returns (uint64 idGiver) { return addGiver( msg.sender, name, url, commitTime, plugin ); } function addGiver( address addr, string name, string url, uint64 commitTime, ILiquidPledgingPlugin plugin ) public returns (uint64 idGiver) { require(isValidPlugin(plugin)); // Plugin check idGiver = uint64(admins.length); // Save the fields admins.push( PledgeAdmin( PledgeAdminType.Giver, addr, commitTime, 0, false, plugin, name, url) ); GiverAdded(idGiver, url); } /// @notice Updates a Giver's info to change the address, name, url, or /// commitTime, it cannot be used to change a plugin, and it must be called /// by the current address of the Giver /// @param idGiver This is the Admin id number used to specify the Giver /// @param newAddr The new address that represents this Giver /// @param newName The new name used to identify the Giver /// @param newUrl The new link to the Giver's profile often an IPFS hash /// @param newCommitTime Sets the length of time in seconds the Giver has to /// veto when the Giver's delegates Pledge funds to a project function updateGiver( uint64 idGiver, address newAddr, string newName, string newUrl, uint64 newCommitTime ) external { PledgeAdmin storage giver = _findAdmin(idGiver); require(msg.sender == giver.addr); require(giver.adminType == PledgeAdminType.Giver); // Must be a Giver giver.addr = newAddr; giver.name = newName; giver.url = newUrl; giver.commitTime = newCommitTime; GiverUpdated(idGiver, newUrl); } /// @notice Creates a Delegate Admin with the `msg.sender` as the Admin addr /// @param name The name used to identify the Delegate /// @param url The link to the Delegate's profile often an IPFS hash /// @param commitTime Sets the length of time in seconds that this delegate /// can be vetoed. Whenever this delegate is in a delegate chain the time /// allowed to veto any event must be greater than or equal to this time. /// @param plugin This is Delegate's liquid pledge plugin allowing for /// extended functionality /// @return idxDelegate The id number used to reference this Delegate within /// the PLEDGE_ADMIN array function addDelegate( string name, string url, uint64 commitTime, ILiquidPledgingPlugin plugin ) external returns (uint64 idDelegate) { require(isValidPlugin(plugin)); // Plugin check idDelegate = uint64(admins.length); admins.push( PledgeAdmin( PledgeAdminType.Delegate, msg.sender, commitTime, 0, false, plugin, name, url) ); DelegateAdded(idDelegate, url); } /// @notice Updates a Delegate's info to change the address, name, url, or /// commitTime, it cannot be used to change a plugin, and it must be called /// by the current address of the Delegate /// @param idDelegate The Admin id number used to specify the Delegate /// @param newAddr The new address that represents this Delegate /// @param newName The new name used to identify the Delegate /// @param newUrl The new link to the Delegate's profile often an IPFS hash /// @param newCommitTime Sets the length of time in seconds that this /// delegate can be vetoed. Whenever this delegate is in a delegate chain /// the time allowed to veto any event must be greater than or equal to /// this time. function updateDelegate( uint64 idDelegate, address newAddr, string newName, string newUrl, uint64 newCommitTime ) external { PledgeAdmin storage delegate = _findAdmin(idDelegate); require(msg.sender == delegate.addr); require(delegate.adminType == PledgeAdminType.Delegate); delegate.addr = newAddr; delegate.name = newName; delegate.url = newUrl; delegate.commitTime = newCommitTime; DelegateUpdated(idDelegate, newUrl); } /// @notice Creates a Project Admin with the `msg.sender` as the Admin addr /// @param name The name used to identify the Project /// @param url The link to the Project's profile often an IPFS hash /// @param projectAdmin The address for the trusted project manager /// @param parentProject The Admin id number for the parent project or 0 if /// there is no parentProject /// @param commitTime Sets the length of time in seconds the Project has to /// veto when the Project delegates to another Delegate and they pledge /// those funds to a project /// @param plugin This is Project's liquid pledge plugin allowing for /// extended functionality /// @return idProject The id number used to reference this Admin function addProject( string name, string url, address projectAdmin, uint64 parentProject, uint64 commitTime, ILiquidPledgingPlugin plugin ) external returns (uint64 idProject) { require(isValidPlugin(plugin)); if (parentProject != 0) { PledgeAdmin storage a = _findAdmin(parentProject); // getProjectLevel will check that parentProject has a `Project` adminType require(_getProjectLevel(a) < MAX_SUBPROJECT_LEVEL); } idProject = uint64(admins.length); admins.push( PledgeAdmin( PledgeAdminType.Project, projectAdmin, commitTime, parentProject, false, plugin, name, url) ); ProjectAdded(idProject, url); } /// @notice Updates a Project's info to change the address, name, url, or /// commitTime, it cannot be used to change a plugin or a parentProject, /// and it must be called by the current address of the Project /// @param idProject The Admin id number used to specify the Project /// @param newAddr The new address that represents this Project /// @param newName The new name used to identify the Project /// @param newUrl The new link to the Project's profile often an IPFS hash /// @param newCommitTime Sets the length of time in seconds the Project has /// to veto when the Project delegates to a Delegate and they pledge those /// funds to a project function updateProject( uint64 idProject, address newAddr, string newName, string newUrl, uint64 newCommitTime ) external { PledgeAdmin storage project = _findAdmin(idProject); require(msg.sender == project.addr); require(project.adminType == PledgeAdminType.Project); project.addr = newAddr; project.name = newName; project.url = newUrl; project.commitTime = newCommitTime; ProjectUpdated(idProject, newUrl); } ///////////////////////////// // Public constant functions ///////////////////////////// /// @notice A constant getter used to check how many total Admins exist /// @return The total number of admins (Givers, Delegates and Projects) . function numberOfPledgeAdmins() external view returns(uint) { return admins.length - 1; } /// @notice A constant getter to check the details of a specified Admin /// @return addr Account or contract address for admin /// @return name Name of the pledgeAdmin /// @return url The link to the Project's profile often an IPFS hash /// @return commitTime The length of time in seconds the Admin has to veto /// when the Admin delegates to a Delegate and that Delegate pledges those /// funds to a project /// @return parentProject The Admin id number for the parent project or 0 /// if there is no parentProject /// @return canceled 0 for Delegates & Givers, true if a Project has been /// canceled /// @return plugin This is Project's liquidPledging plugin allowing for /// extended functionality function getPledgeAdmin(uint64 idAdmin) external view returns ( PledgeAdminType adminType, address addr, string name, string url, uint64 commitTime, uint64 parentProject, bool canceled, address plugin ) { PledgeAdmin storage a = _findAdmin(idAdmin); adminType = a.adminType; addr = a.addr; name = a.name; url = a.url; commitTime = a.commitTime; parentProject = a.parentProject; canceled = a.canceled; plugin = address(a.plugin); } /// @notice A getter to find if a specified Project has been canceled /// @param projectId The Admin id number used to specify the Project /// @return True if the Project has been canceled function isProjectCanceled(uint64 projectId) public view returns (bool) { PledgeAdmin storage a = _findAdmin(projectId); if (a.adminType == PledgeAdminType.Giver) { return false; } assert(a.adminType == PledgeAdminType.Project); if (a.canceled) { return true; } if (a.parentProject == 0) { return false; } return isProjectCanceled(a.parentProject); } /////////////////// // Internal methods /////////////////// /// @notice A getter to look up a Admin's details /// @param idAdmin The id for the Admin to lookup /// @return The PledgeAdmin struct for the specified Admin function _findAdmin(uint64 idAdmin) internal view returns (PledgeAdmin storage) { require(idAdmin < admins.length); return admins[idAdmin]; } /// @notice Find the level of authority a specific Project has /// using a recursive loop /// @param a The project admin being queried /// @return The level of authority a specific Project has function _getProjectLevel(PledgeAdmin a) internal view returns(uint64) { assert(a.adminType == PledgeAdminType.Project); if (a.parentProject == 0) { return(1); } PledgeAdmin storage parent = _findAdmin(a.parentProject); return _getProjectLevel(parent) + 1; } } ///File: ./contracts/Pledges.sol pragma solidity ^0.4.18; /* Copyright 2017, Jordi Baylina, RJ Ewing Contributors: Adrià Massanet , Griff Green, Arthur Lunn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ contract Pledges is AragonApp, LiquidPledgingStorage { // Limits inserted to prevent large loops that could prevent canceling uint constant MAX_DELEGATES = 10; // a constant for when a delegate is requested that is not in the system uint64 constant NOTFOUND = 0xFFFFFFFFFFFFFFFF; ///////////////////////////// // Public constant functions //////////////////////////// /// @notice A constant getter that returns the total number of pledges /// @return The total number of Pledges in the system function numberOfPledges() external view returns (uint) { return pledges.length - 1; } /// @notice A getter that returns the details of the specified pledge /// @param idPledge the id number of the pledge being queried /// @return the amount, owner, the number of delegates (but not the actual /// delegates, the intendedProject (if any), the current commit time and /// the previous pledge this pledge was derived from function getPledge(uint64 idPledge) external view returns( uint amount, uint64 owner, uint64 nDelegates, uint64 intendedProject, uint64 commitTime, uint64 oldPledge, address token, PledgeState pledgeState ) { Pledge memory p = _findPledge(idPledge); amount = p.amount; owner = p.owner; nDelegates = uint64(p.delegationChain.length); intendedProject = p.intendedProject; commitTime = p.commitTime; oldPledge = p.oldPledge; token = p.token; pledgeState = p.pledgeState; } //////////////////// // Internal methods //////////////////// /// @notice This creates a Pledge with an initial amount of 0 if one is not /// created already; otherwise it finds the pledge with the specified /// attributes; all pledges technically exist, if the pledge hasn't been /// created in this system yet it simply isn't in the hash array /// hPledge2idx[] yet /// @param owner The owner of the pledge being looked up /// @param delegationChain The list of delegates in order of authority /// @param intendedProject The project this pledge will Fund after the /// commitTime has passed /// @param commitTime The length of time in seconds the Giver has to /// veto when the Giver's delegates Pledge funds to a project /// @param oldPledge This value is used to store the pledge the current /// pledge was came from, and in the case a Project is canceled, the Pledge /// will revert back to it's previous state /// @param state The pledge state: Pledged, Paying, or state /// @return The hPledge2idx index number function _findOrCreatePledge( uint64 owner, uint64[] delegationChain, uint64 intendedProject, uint64 commitTime, uint64 oldPledge, address token, PledgeState state ) internal returns (uint64) { bytes32 hPledge = keccak256(delegationChain, owner, intendedProject, commitTime, oldPledge, token, state); uint64 id = hPledge2idx[hPledge]; if (id > 0) { return id; } id = uint64(pledges.length); hPledge2idx[hPledge] = id; pledges.push( Pledge( 0, delegationChain, owner, intendedProject, commitTime, oldPledge, token, state ) ); return id; } /// @param idPledge the id of the pledge to load from storage /// @return The Pledge function _findPledge(uint64 idPledge) internal view returns(Pledge storage) { require(idPledge < pledges.length); return pledges[idPledge]; } /// @notice A getter that searches the delegationChain for the level of /// authority a specific delegate has within a Pledge /// @param p The Pledge that will be searched /// @param idDelegate The specified delegate that's searched for /// @return If the delegate chain contains the delegate with the /// `admins` array index `idDelegate` this returns that delegates /// corresponding index in the delegationChain. Otherwise it returns /// the NOTFOUND constant function _getDelegateIdx(Pledge p, uint64 idDelegate) internal pure returns(uint64) { for (uint i = 0; i < p.delegationChain.length; i++) { if (p.delegationChain[i] == idDelegate) { return uint64(i); } } return NOTFOUND; } /// @notice A getter to find how many old "parent" pledges a specific Pledge /// had using a self-referential loop /// @param p The Pledge being queried /// @return The number of old "parent" pledges a specific Pledge had function _getPledgeLevel(Pledge p) internal view returns(uint) { if (p.oldPledge == 0) { return 0; } Pledge storage oldP = _findPledge(p.oldPledge); return _getPledgeLevel(oldP) + 1; // a loop lookup } } ///File: ./contracts/LiquidPledgingBase.sol pragma solidity ^0.4.18; /* Copyright 2017, Jordi Baylina Contributors: Adrià Massanet , RJ Ewing, Griff Green, Arthur Lunn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /// @dev `LiquidPledgingBase` is the base level contract used to carry out /// liquidPledging's most basic functions, mostly handling and searching the /// data structures contract LiquidPledgingBase is AragonApp, LiquidPledgingStorage, PledgeAdmins, Pledges { event Transfer(uint indexed from, uint indexed to, uint amount); event CancelProject(uint indexed idProject); ///////////// // Modifiers ///////////// /// @dev The `vault`is the only addresses that can call a function with this /// modifier modifier onlyVault() { require(msg.sender == address(vault)); _; } /////////////// // Constructor /////////////// /// @param _vault The vault where the ETH backing the pledges is stored function initialize(address _vault) onlyInit public { require(_vault != 0x0); initialized(); vault = ILPVault(_vault); admins.length = 1; // we reserve the 0 admin pledges.length = 1; // we reserve the 0 pledge } ///////////////////////////// // Public constant functions ///////////////////////////// /// @notice Getter to find Delegate w/ the Pledge ID & the Delegate index /// @param idPledge The id number representing the pledge being queried /// @param idxDelegate The index number for the delegate in this Pledge function getPledgeDelegate(uint64 idPledge, uint64 idxDelegate) external view returns( uint64 idDelegate, address addr, string name ) { Pledge storage p = _findPledge(idPledge); idDelegate = p.delegationChain[idxDelegate - 1]; PledgeAdmin storage delegate = _findAdmin(idDelegate); addr = delegate.addr; name = delegate.name; } /////////////////// // Public functions /////////////////// /// @notice Only affects pledges with the Pledged PledgeState for 2 things: /// #1: Checks if the pledge should be committed. This means that /// if the pledge has an intendedProject and it is past the /// commitTime, it changes the owner to be the proposed project /// (The UI will have to read the commit time and manually do what /// this function does to the pledge for the end user /// at the expiration of the commitTime) /// /// #2: Checks to make sure that if there has been a cancellation in the /// chain of projects, the pledge's owner has been changed /// appropriately. /// /// This function can be called by anybody at anytime on any pledge. /// In general it can be called to force the calls of the affected /// plugins, which also need to be predicted by the UI /// @param idPledge This is the id of the pledge that will be normalized /// @return The normalized Pledge! function normalizePledge(uint64 idPledge) public returns(uint64) { Pledge storage p = _findPledge(idPledge); // Check to make sure this pledge hasn't already been used // or is in the process of being used if (p.pledgeState != PledgeState.Pledged) { return idPledge; } // First send to a project if it's proposed and committed if ((p.intendedProject > 0) && ( _getTime() > p.commitTime)) { uint64 oldPledge = _findOrCreatePledge( p.owner, p.delegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Pledged ); uint64 toPledge = _findOrCreatePledge( p.intendedProject, new uint64[](0), 0, 0, oldPledge, p.token, PledgeState.Pledged ); _doTransfer(idPledge, toPledge, p.amount); idPledge = toPledge; p = _findPledge(idPledge); } toPledge = _getOldestPledgeNotCanceled(idPledge); if (toPledge != idPledge) { _doTransfer(idPledge, toPledge, p.amount); } return toPledge; } //////////////////// // Internal methods //////////////////// /// @notice A check to see if the msg.sender is the owner or the /// plugin contract for a specific Admin /// @param idAdmin The id of the admin being checked function _checkAdminOwner(uint64 idAdmin) internal view { PledgeAdmin storage a = _findAdmin(idAdmin); require(msg.sender == address(a.plugin) || msg.sender == a.addr); } function _transfer( uint64 idSender, uint64 idPledge, uint amount, uint64 idReceiver ) internal { require(idReceiver > 0); // prevent burning value idPledge = normalizePledge(idPledge); Pledge storage p = _findPledge(idPledge); PledgeAdmin storage receiver = _findAdmin(idReceiver); require(p.pledgeState == PledgeState.Pledged); // If the sender is the owner of the Pledge if (p.owner == idSender) { if (receiver.adminType == PledgeAdminType.Giver) { _transferOwnershipToGiver(idPledge, amount, idReceiver); return; } else if (receiver.adminType == PledgeAdminType.Project) { _transferOwnershipToProject(idPledge, amount, idReceiver); return; } else if (receiver.adminType == PledgeAdminType.Delegate) { uint recieverDIdx = _getDelegateIdx(p, idReceiver); if (p.intendedProject > 0 && recieverDIdx != NOTFOUND) { // if there is an intendedProject and the receiver is in the delegationChain, // then we want to preserve the delegationChain as this is a veto of the // intendedProject by the owner if (recieverDIdx == p.delegationChain.length - 1) { uint64 toPledge = _findOrCreatePledge( p.owner, p.delegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Pledged); _doTransfer(idPledge, toPledge, amount); return; } _undelegate(idPledge, amount, p.delegationChain.length - receiverDIdx - 1); return; } // owner is not vetoing an intendedProject and is transferring the pledge to a delegate, // so we want to reset the delegationChain idPledge = _undelegate( idPledge, amount, p.delegationChain.length ); _appendDelegate(idPledge, amount, idReceiver); return; } // This should never be reached as the receiver.adminType // should always be either a Giver, Project, or Delegate assert(false); } // If the sender is a Delegate uint senderDIdx = _getDelegateIdx(p, idSender); if (senderDIdx != NOTFOUND) { // And the receiver is another Giver if (receiver.adminType == PledgeAdminType.Giver) { // Only transfer to the Giver who owns the pledge assert(p.owner == idReceiver); _undelegate(idPledge, amount, p.delegationChain.length); return; } // And the receiver is another Delegate if (receiver.adminType == PledgeAdminType.Delegate) { uint receiverDIdx = _getDelegateIdx(p, idReceiver); // And not in the delegationChain or part of the delegationChain // is after the sender, then all of the other delegates after // the sender are removed and the receiver is appended at the // end of the delegationChain if (receiverDIdx == NOTFOUND || receiverDIdx > senderDIdx) { idPledge = _undelegate( idPledge, amount, p.delegationChain.length - senderDIdx - 1 ); _appendDelegate(idPledge, amount, idReceiver); return; } // And is already part of the delegate chain but is before the // sender, then the sender and all of the other delegates after // the RECEIVER are removed from the delegationChain _undelegate( idPledge, amount, p.delegationChain.length - receiverDIdx - 1 ); return; } // And the receiver is a Project, all the delegates after the sender // are removed and the amount is pre-committed to the project if (receiver.adminType == PledgeAdminType.Project) { idPledge = _undelegate( idPledge, amount, p.delegationChain.length - senderDIdx - 1 ); _proposeAssignProject(idPledge, amount, idReceiver); return; } } assert(false); // When the sender is not an owner or a delegate } /// @notice `transferOwnershipToProject` allows for the transfer of /// ownership to the project, but it can also be called by a project /// to un-delegate everyone by setting one's own id for the idReceiver /// @param idPledge the id of the pledge to be transfered. /// @param amount Quantity of value that's being transfered /// @param idReceiver The new owner of the project (or self to un-delegate) function _transferOwnershipToProject( uint64 idPledge, uint amount, uint64 idReceiver ) internal { Pledge storage p = _findPledge(idPledge); // Ensure that the pledge is not already at max pledge depth // and the project has not been canceled require(_getPledgeLevel(p) < MAX_INTERPROJECT_LEVEL); require(!isProjectCanceled(idReceiver)); uint64 oldPledge = _findOrCreatePledge( p.owner, p.delegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Pledged ); uint64 toPledge = _findOrCreatePledge( idReceiver, // Set the new owner new uint64[](0), // clear the delegation chain 0, 0, oldPledge, p.token, PledgeState.Pledged ); _doTransfer(idPledge, toPledge, amount); } /// @notice `transferOwnershipToGiver` allows for the transfer of /// value back to the Giver, value is placed in a pledged state /// without being attached to a project, delegation chain, or time line. /// @param idPledge the id of the pledge to be transferred. /// @param amount Quantity of value that's being transferred /// @param idReceiver The new owner of the pledge function _transferOwnershipToGiver( uint64 idPledge, uint amount, uint64 idReceiver ) internal { Pledge storage p = _findPledge(idPledge); uint64 toPledge = _findOrCreatePledge( idReceiver, new uint64[](0), 0, 0, 0, p.token, PledgeState.Pledged ); _doTransfer(idPledge, toPledge, amount); } /// @notice `appendDelegate` allows for a delegate to be added onto the /// end of the delegate chain for a given Pledge. /// @param idPledge the id of the pledge thats delegate chain will be modified. /// @param amount Quantity of value that's being chained. /// @param idReceiver The delegate to be added at the end of the chain function _appendDelegate( uint64 idPledge, uint amount, uint64 idReceiver ) internal { Pledge storage p = _findPledge(idPledge); require(p.delegationChain.length < MAX_DELEGATES); uint64[] memory newDelegationChain = new uint64[]( p.delegationChain.length + 1 ); for (uint i = 0; i < p.delegationChain.length; i++) { newDelegationChain[i] = p.delegationChain[i]; } // Make the last item in the array the idReceiver newDelegationChain[p.delegationChain.length] = idReceiver; uint64 toPledge = _findOrCreatePledge( p.owner, newDelegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Pledged ); _doTransfer(idPledge, toPledge, amount); } /// @notice `appendDelegate` allows for a delegate to be added onto the /// end of the delegate chain for a given Pledge. /// @param idPledge the id of the pledge thats delegate chain will be modified. /// @param amount Quantity of value that's shifted from delegates. /// @param q Number (or depth) of delegates to remove /// @return toPledge The id for the pledge being adjusted or created function _undelegate( uint64 idPledge, uint amount, uint q ) internal returns (uint64 toPledge) { Pledge storage p = _findPledge(idPledge); uint64[] memory newDelegationChain = new uint64[]( p.delegationChain.length - q ); for (uint i = 0; i < p.delegationChain.length - q; i++) { newDelegationChain[i] = p.delegationChain[i]; } toPledge = _findOrCreatePledge( p.owner, newDelegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Pledged ); _doTransfer(idPledge, toPledge, amount); } /// @notice `proposeAssignProject` proposes the assignment of a pledge /// to a specific project. /// @dev This function should potentially be named more specifically. /// @param idPledge the id of the pledge that will be assigned. /// @param amount Quantity of value this pledge leader would be assigned. /// @param idReceiver The project this pledge will potentially /// be assigned to. function _proposeAssignProject( uint64 idPledge, uint amount, uint64 idReceiver ) internal { Pledge storage p = _findPledge(idPledge); require(_getPledgeLevel(p) < MAX_INTERPROJECT_LEVEL); require(!isProjectCanceled(idReceiver)); uint64 toPledge = _findOrCreatePledge( p.owner, p.delegationChain, idReceiver, uint64(_getTime() + _maxCommitTime(p)), p.oldPledge, p.token, PledgeState.Pledged ); _doTransfer(idPledge, toPledge, amount); } /// @notice `doTransfer` is designed to allow for pledge amounts to be /// shifted around internally. /// @param from This is the id of the pledge from which value will be transferred. /// @param to This is the id of the pledge that value will be transferred to. /// @param _amount The amount of value that will be transferred. function _doTransfer(uint64 from, uint64 to, uint _amount) internal { uint amount = _callPlugins(true, from, to, _amount); if (from == to) { return; } if (amount == 0) { return; } Pledge storage pFrom = _findPledge(from); Pledge storage pTo = _findPledge(to); require(pFrom.amount >= amount); pFrom.amount -= amount; pTo.amount += amount; require(pTo.amount >= amount); Transfer(from, to, amount); _callPlugins(false, from, to, amount); } /// @notice A getter to find the longest commitTime out of the owner and all /// the delegates for a specified pledge /// @param p The Pledge being queried /// @return The maximum commitTime out of the owner and all the delegates function _maxCommitTime(Pledge p) internal view returns(uint64 commitTime) { PledgeAdmin storage a = _findAdmin(p.owner); commitTime = a.commitTime; // start with the owner's commitTime for (uint i = 0; i < p.delegationChain.length; i++) { a = _findAdmin(p.delegationChain[i]); // If a delegate's commitTime is longer, make it the new commitTime if (a.commitTime > commitTime) { commitTime = a.commitTime; } } } /// @notice A getter to find the oldest pledge that hasn't been canceled /// @param idPledge The starting place to lookup the pledges /// @return The oldest idPledge that hasn't been canceled (DUH!) function _getOldestPledgeNotCanceled( uint64 idPledge ) internal view returns(uint64) { if (idPledge == 0) { return 0; } Pledge storage p = _findPledge(idPledge); PledgeAdmin storage admin = _findAdmin(p.owner); if (admin.adminType == PledgeAdminType.Giver) { return idPledge; } assert(admin.adminType == PledgeAdminType.Project); if (!isProjectCanceled(p.owner)) { return idPledge; } return _getOldestPledgeNotCanceled(p.oldPledge); } /// @notice `callPlugin` is used to trigger the general functions in the /// plugin for any actions needed before and after a transfer happens. /// Specifically what this does in relation to the plugin is something /// that largely depends on the functions of that plugin. This function /// is generally called in pairs, once before, and once after a transfer. /// @param before This toggle determines whether the plugin call is occurring /// before or after a transfer. /// @param adminId This should be the Id of the *trusted* individual /// who has control over this plugin. /// @param fromPledge This is the Id from which value is being transfered. /// @param toPledge This is the Id that value is being transfered to. /// @param context The situation that is triggering the plugin. See plugin /// for a full description of contexts. /// @param amount The amount of value that is being transfered. function _callPlugin( bool before, uint64 adminId, uint64 fromPledge, uint64 toPledge, uint64 context, address token, uint amount ) internal returns (uint allowedAmount) { uint newAmount; allowedAmount = amount; PledgeAdmin storage admin = _findAdmin(adminId); // Checks admin has a plugin assigned and a non-zero amount is requested if (address(admin.plugin) != 0 && allowedAmount > 0) { // There are two separate functions called in the plugin. // One is called before the transfer and one after if (before) { newAmount = admin.plugin.beforeTransfer( adminId, fromPledge, toPledge, context, token, amount ); require(newAmount <= allowedAmount); allowedAmount = newAmount; } else { admin.plugin.afterTransfer( adminId, fromPledge, toPledge, context, token, amount ); } } } /// @notice `callPluginsPledge` is used to apply plugin calls to /// the delegate chain and the intended project if there is one. /// It does so in either a transferring or receiving context based /// on the `p` and `fromPledge` parameters. /// @param before This toggle determines whether the plugin call is occuring /// before or after a transfer. /// @param idPledge This is the id of the pledge on which this plugin /// is being called. /// @param fromPledge This is the Id from which value is being transfered. /// @param toPledge This is the Id that value is being transfered to. /// @param amount The amount of value that is being transfered. function _callPluginsPledge( bool before, uint64 idPledge, uint64 fromPledge, uint64 toPledge, uint amount ) internal returns (uint allowedAmount) { // Determine if callPlugin is being applied in a receiving // or transferring context uint64 offset = idPledge == fromPledge ? 0 : 256; allowedAmount = amount; Pledge storage p = _findPledge(idPledge); // Always call the plugin on the owner allowedAmount = _callPlugin( before, p.owner, fromPledge, toPledge, offset, p.token, allowedAmount ); // Apply call plugin to all delegates for (uint64 i = 0; i < p.delegationChain.length; i++) { allowedAmount = _callPlugin( before, p.delegationChain[i], fromPledge, toPledge, offset + i + 1, p.token, allowedAmount ); } // If there is an intended project also call the plugin in // either a transferring or receiving context based on offset // on the intended project if (p.intendedProject > 0) { allowedAmount = _callPlugin( before, p.intendedProject, fromPledge, toPledge, offset + 255, p.token, allowedAmount ); } } /// @notice `callPlugins` calls `callPluginsPledge` once for the transfer /// context and once for the receiving context. The aggregated /// allowed amount is then returned. /// @param before This toggle determines whether the plugin call is occurring /// before or after a transfer. /// @param fromPledge This is the Id from which value is being transferred. /// @param toPledge This is the Id that value is being transferred to. /// @param amount The amount of value that is being transferred. function _callPlugins( bool before, uint64 fromPledge, uint64 toPledge, uint amount ) internal returns (uint allowedAmount) { allowedAmount = amount; // Call the plugins in the transfer context allowedAmount = _callPluginsPledge( before, fromPledge, fromPledge, toPledge, allowedAmount ); // Call the plugins in the receive context allowedAmount = _callPluginsPledge( before, toPledge, fromPledge, toPledge, allowedAmount ); } ///////////// // Test functions ///////////// /// @notice Basic helper function to return the current time function _getTime() internal view returns (uint) { return now; } } ///File: ./contracts/LiquidPledging.sol pragma solidity ^0.4.18; /* Copyright 2017, Jordi Baylina, RJ Ewing Contributors: Adrià Massanet , Griff Green, Arthur Lunn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /// @dev `LiquidPledging` allows for liquid pledging through the use of /// internal id structures and delegate chaining. All basic operations for /// handling liquid pledging are supplied as well as plugin features /// to allow for expanded functionality. contract LiquidPledging is LiquidPledgingBase { /// Create a "giver" pledge admin for the sender & donate /// @param idReceiver The Admin receiving the donation; can be any Admin: /// the Giver themselves, another Giver, a Delegate or a Project /// @param token The address of the token being donated. /// @param amount The amount of tokens being donated function addGiverAndDonate(uint64 idReceiver, address token, uint amount) public { addGiverAndDonate(idReceiver, msg.sender, token, amount); } /// Create a "giver" pledge admin for the given `donorAddress` & donate /// @param idReceiver The Admin receiving the donation; can be any Admin: /// the Giver themselves, another Giver, a Delegate or a Project /// @param donorAddress The address of the "giver" of this donation /// @param token The address of the token being donated. /// @param amount The amount of tokens being donated function addGiverAndDonate(uint64 idReceiver, address donorAddress, address token, uint amount) public { require(donorAddress != 0); // default to a 3 day (259200 seconds) commitTime uint64 idGiver = addGiver(donorAddress, "", "", 259200, ILiquidPledgingPlugin(0)); donate(idGiver, idReceiver, token, amount); } /// @notice This is how value enters the system and how pledges are created; /// the ether is sent to the vault, an pledge for the Giver is created (or /// found), the amount of ETH donated in wei is added to the `amount` in /// the Giver's Pledge, and an LP transfer is done to the idReceiver for /// the full amount /// @param idGiver The id of the Giver donating /// @param idReceiver The Admin receiving the donation; can be any Admin: /// the Giver themselves, another Giver, a Delegate or a Project /// @param token The address of the token being donated. /// @param amount The amount of tokens being donated function donate(uint64 idGiver, uint64 idReceiver, address token, uint amount) public { require(idGiver > 0); // prevent burning donations. idReceiver is checked in _transfer require(amount > 0); require(token != 0x0); PledgeAdmin storage sender = _findAdmin(idGiver); require(sender.adminType == PledgeAdminType.Giver); require(ERC20(token).transferFrom(msg.sender, address(vault), amount)); // transfer the token to the `vault` uint64 idPledge = _findOrCreatePledge( idGiver, new uint64[](0), // Creates empty array for delegationChain 0, 0, 0, token, PledgeState.Pledged ); Pledge storage pTo = _findPledge(idPledge); pTo.amount += amount; Transfer(0, idPledge, amount); _transfer(idGiver, idPledge, amount, idReceiver); } /// @notice Transfers amounts between pledges for internal accounting /// @param idSender Id of the Admin that is transferring the amount from /// Pledge to Pledge; this admin must have permissions to move the value /// @param idPledge Id of the pledge that's moving the value /// @param amount Quantity of ETH (in wei) that this pledge is transferring /// the authority to withdraw from the vault /// @param idReceiver Destination of the `amount`, can be a Giver/Project sending /// to a Giver, a Delegate or a Project; a Delegate sending to another /// Delegate, or a Delegate pre-commiting it to a Project function transfer( uint64 idSender, uint64 idPledge, uint amount, uint64 idReceiver ) public { _checkAdminOwner(idSender); _transfer(idSender, idPledge, amount, idReceiver); } /// @notice Authorizes a payment be made from the `vault` can be used by the /// Giver to veto a pre-committed donation from a Delegate to an /// intendedProject /// @param idPledge Id of the pledge that is to be redeemed into ether /// @param amount Quantity of ether (in wei) to be authorized function withdraw(uint64 idPledge, uint amount) public { idPledge = normalizePledge(idPledge); // Updates pledge info Pledge storage p = _findPledge(idPledge); require(p.pledgeState == PledgeState.Pledged); _checkAdminOwner(p.owner); uint64 idNewPledge = _findOrCreatePledge( p.owner, p.delegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Paying ); _doTransfer(idPledge, idNewPledge, amount); PledgeAdmin storage owner = _findAdmin(p.owner); vault.authorizePayment(bytes32(idNewPledge), owner.addr, p.token, amount); } /// @notice `onlyVault` Confirms a withdraw request changing the PledgeState /// from Paying to Paid /// @param idPledge Id of the pledge that is to be withdrawn /// @param amount Quantity of ether (in wei) to be withdrawn function confirmPayment(uint64 idPledge, uint amount) public onlyVault { Pledge storage p = _findPledge(idPledge); require(p.pledgeState == PledgeState.Paying); uint64 idNewPledge = _findOrCreatePledge( p.owner, p.delegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Paid ); _doTransfer(idPledge, idNewPledge, amount); } /// @notice `onlyVault` Cancels a withdraw request, changing the PledgeState /// from Paying back to Pledged /// @param idPledge Id of the pledge that's withdraw is to be canceled /// @param amount Quantity of ether (in wei) to be canceled function cancelPayment(uint64 idPledge, uint amount) public onlyVault { Pledge storage p = _findPledge(idPledge); require(p.pledgeState == PledgeState.Paying); // When a payment is canceled, never is assigned to a project. uint64 idOldPledge = _findOrCreatePledge( p.owner, p.delegationChain, 0, 0, p.oldPledge, p.token, PledgeState.Pledged ); idOldPledge = normalizePledge(idOldPledge); _doTransfer(idPledge, idOldPledge, amount); } /// @notice Changes the `project.canceled` flag to `true`; cannot be undone /// @param idProject Id of the project that is to be canceled function cancelProject(uint64 idProject) public { PledgeAdmin storage project = _findAdmin(idProject); _checkAdminOwner(idProject); project.canceled = true; CancelProject(idProject); } /// @notice Transfers `amount` in `idPledge` back to the `oldPledge` that /// that sent it there in the first place, a Ctrl-z /// @param idPledge Id of the pledge that is to be canceled /// @param amount Quantity of ether (in wei) to be transfered to the /// `oldPledge` function cancelPledge(uint64 idPledge, uint amount) public { idPledge = normalizePledge(idPledge); Pledge storage p = _findPledge(idPledge); require(p.oldPledge != 0); require(p.pledgeState == PledgeState.Pledged); _checkAdminOwner(p.owner); uint64 oldPledge = _getOldestPledgeNotCanceled(p.oldPledge); _doTransfer(idPledge, oldPledge, amount); } //////// // Multi pledge methods //////// // @dev This set of functions makes moving a lot of pledges around much more // efficient (saves gas) than calling these functions in series /// @dev Bitmask used for dividing pledge amounts in Multi pledge methods uint constant D64 = 0x10000000000000000; /// @notice Transfers multiple amounts within multiple Pledges in an /// efficient single call /// @param idSender Id of the Admin that is transferring the amounts from /// all the Pledges; this admin must have permissions to move the value /// @param pledgesAmounts An array of Pledge amounts and the idPledges with /// which the amounts are associated; these are extrapolated using the D64 /// bitmask /// @param idReceiver Destination of the `pledesAmounts`, can be a Giver or /// Project sending to a Giver, a Delegate or a Project; a Delegate sending /// to another Delegate, or a Delegate pre-commiting it to a Project function mTransfer( uint64 idSender, uint[] pledgesAmounts, uint64 idReceiver ) public { for (uint i = 0; i < pledgesAmounts.length; i++ ) { uint64 idPledge = uint64(pledgesAmounts[i] & (D64-1)); uint amount = pledgesAmounts[i] / D64; transfer(idSender, idPledge, amount, idReceiver); } } /// @notice Authorizes multiple amounts within multiple Pledges to be /// withdrawn from the `vault` in an efficient single call /// @param pledgesAmounts An array of Pledge amounts and the idPledges with /// which the amounts are associated; these are extrapolated using the D64 /// bitmask function mWithdraw(uint[] pledgesAmounts) public { for (uint i = 0; i < pledgesAmounts.length; i++ ) { uint64 idPledge = uint64(pledgesAmounts[i] & (D64-1)); uint amount = pledgesAmounts[i] / D64; withdraw(idPledge, amount); } } /// @notice `mNormalizePledge` allows for multiple pledges to be /// normalized efficiently /// @param pledges An array of pledge IDs function mNormalizePledge(uint64[] pledges) public { for (uint i = 0; i < pledges.length; i++ ) { normalizePledge(pledges[i]); } } } ///File: ./contracts/test/TestSimpleDelegatePlugin.sol pragma solidity ^0.4.18; // simple liquidPledging plugin contract for testing whitelist contract TestSimpleDelegatePlugin { uint64 public idDelegate; LiquidPledging liquidPledging; bool initPending; event BeforeTransfer(uint64 pledgeAdmin, uint64 pledgeFrom, uint64 pledgeTo, uint64 context, uint amount); event AfterTransfer(uint64 pledgeAdmin, uint64 pledgeFrom, uint64 pledgeTo, uint64 context, uint amount); function TestSimpleDelegatePlugin(LiquidPledging _liquidPledging) public { require(msg.sender != tx.origin); // Avoids being created directly by mistake. liquidPledging = _liquidPledging; initPending = true; } function init( string name, string url, uint64 commitTime ) public { require(initPending); idDelegate = liquidPledging.addDelegate(name, url, commitTime, ILiquidPledgingPlugin(this)); initPending = false; } function beforeTransfer( uint64 pledgeAdmin, uint64 pledgeFrom, uint64 pledgeTo, uint64 context, uint amount ) external returns (uint maxAllowed) { require(!initPending); maxAllowed; BeforeTransfer(pledgeAdmin, pledgeFrom, pledgeTo, context, amount); } function afterTransfer( uint64 pledgeAdmin, uint64 pledgeFrom, uint64 pledgeTo, uint64 context, uint amount ) external { require(!initPending); AfterTransfer(pledgeAdmin, pledgeFrom, pledgeTo, context, amount); } } contract TestSimpleDelegatePluginFactory { function TestSimpleDelegatePluginFactory( LiquidPledging liquidPledging, string name, string url, uint64 commitTime ) public { TestSimpleDelegatePlugin d = new TestSimpleDelegatePlugin(liquidPledging); d.init(name, url, commitTime); } }