Merge branch 'aragon'
This commit is contained in:
commit
24ec03a4fe
|
@ -1,3 +1,4 @@
|
|||
build/
|
||||
lib/
|
||||
node_modules/
|
||||
.DS_Store
|
||||
|
@ -8,4 +9,3 @@ __pycache__
|
|||
.idea
|
||||
*.iml
|
||||
|
||||
build/
|
|
@ -12,7 +12,7 @@
|
|||
"no-with": true,
|
||||
"no-empty-blocks": true,
|
||||
"no-unused-vars": true,
|
||||
"double-quotes": true,
|
||||
"quotes": true,
|
||||
"blank-lines": true,
|
||||
"indentation": true,
|
||||
"whitespace": true,
|
||||
|
|
|
@ -17,7 +17,7 @@ Welcome to the code for the liquidpledging contract, a new way to distribute don
|
|||
|
||||
### Install
|
||||
1. Click **Star** on this repo near the top-right corner of this web page (if you want to).
|
||||
2. Join our [slack](http://slack.giveth.io) if you haven't already.
|
||||
2. Join our [Riot](http://join.giveth.io) if you haven't already.
|
||||
3. Fork this repo by clicking **Fork** button in top-right corner of this web page. Continue to follow instruction steps from your own liquidpledging repo.
|
||||
5. The rest of these steps must be done from your machine's command line. Clone your own "liquidpledging" repo:
|
||||
```
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
pragma solidity ^0.4.18;
|
||||
/*
|
||||
Copyright 2016, Jordi Baylina
|
||||
Contributor: Adrià Massanet <adria@codecontext.io>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// import "./Owned.sol";
|
||||
import "giveth-common-contracts/contracts/ERC20.sol";
|
||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
||||
|
||||
|
||||
/// @dev `EscapableApp` is a base level contract; it creates an escape hatch
|
||||
/// function that can be called in an
|
||||
/// emergency that will allow designated addresses to send any ether or tokens
|
||||
/// held in the contract to an `escapeHatchDestination` as long as they were
|
||||
/// not blacklisted
|
||||
contract EscapableApp is AragonApp {
|
||||
// warning whoever has this role can move all funds to the `escapeHatchDestination`
|
||||
bytes32 constant public ESCAPE_HATCH_CALLER_ROLE = keccak256("ESCAPE_HATCH_CALLER_ROLE");
|
||||
|
||||
event EscapeHatchBlackistedToken(address token);
|
||||
event EscapeHatchCalled(address token, uint amount);
|
||||
|
||||
address public escapeHatchDestination;
|
||||
mapping (address=>bool) private escapeBlacklist; // Token contract addresses
|
||||
uint[20] private storageOffset; // reserve 20 slots for future upgrades
|
||||
|
||||
function EscapableApp(address _escapeHatchDestination) public {
|
||||
_init(_escapeHatchDestination);
|
||||
}
|
||||
|
||||
/// @param _escapeHatchDestination The address of a safe location (usu a
|
||||
/// Multisig) to send the ether held in this contract; if a neutral address
|
||||
/// is required, the WHG Multisig is an option:
|
||||
/// 0x8Ff920020c8AD673661c8117f2855C384758C572
|
||||
function initialize(address _escapeHatchDestination) onlyInit public {
|
||||
_init(_escapeHatchDestination);
|
||||
}
|
||||
|
||||
/// @notice The `escapeHatch()` should only be called as a last resort if a
|
||||
/// security issue is uncovered or something unexpected happened
|
||||
/// @param _token to transfer, use 0x0 for ether
|
||||
function escapeHatch(address _token) external authP(ESCAPE_HATCH_CALLER_ROLE, arr(_token)) {
|
||||
require(escapeBlacklist[_token]==false);
|
||||
|
||||
uint256 balance;
|
||||
|
||||
/// @dev Logic for ether
|
||||
if (_token == 0x0) {
|
||||
balance = this.balance;
|
||||
escapeHatchDestination.transfer(balance);
|
||||
EscapeHatchCalled(_token, balance);
|
||||
return;
|
||||
}
|
||||
/// @dev Logic for tokens
|
||||
ERC20 token = ERC20(_token);
|
||||
balance = token.balanceOf(this);
|
||||
require(token.transfer(escapeHatchDestination, balance));
|
||||
EscapeHatchCalled(_token, balance);
|
||||
}
|
||||
|
||||
/// @notice Checks to see if `_token` is in the blacklist of tokens
|
||||
/// @param _token the token address being queried
|
||||
/// @return False if `_token` is in the blacklist and can't be taken out of
|
||||
/// the contract via the `escapeHatch()`
|
||||
function isTokenEscapable(address _token) view external returns (bool) {
|
||||
return !escapeBlacklist[_token];
|
||||
}
|
||||
|
||||
function _init(address _escapeHatchDestination) internal {
|
||||
initialized();
|
||||
require(_escapeHatchDestination != 0x0);
|
||||
|
||||
escapeHatchDestination = _escapeHatchDestination;
|
||||
}
|
||||
|
||||
/// @notice Creates the blacklist of tokens that are not able to be taken
|
||||
/// out of the contract; can only be done at the deployment, and the logic
|
||||
/// to add to the blacklist will be in the constructor of a child contract
|
||||
/// @param _token the token contract address that is to be blacklisted
|
||||
function _blacklistEscapeToken(address _token) internal {
|
||||
escapeBlacklist[_token] = true;
|
||||
EscapeHatchBlackistedToken(_token);
|
||||
}
|
||||
}
|
|
@ -49,7 +49,8 @@ contract ILiquidPledgingPlugin {
|
|||
uint64 pledgeFrom,
|
||||
uint64 pledgeTo,
|
||||
uint64 context,
|
||||
uint amount ) returns (uint maxAllowed);
|
||||
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
|
||||
|
@ -76,6 +77,7 @@ contract ILiquidPledgingPlugin {
|
|||
uint64 pledgeFrom,
|
||||
uint64 pledgeTo,
|
||||
uint64 context,
|
||||
address token,
|
||||
uint amount
|
||||
);
|
||||
) public;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
import "@aragon/os/contracts/kernel/KernelStorage.sol";
|
||||
|
||||
contract LPConstants is KernelConstants {
|
||||
bytes32 constant public VAULT_APP_ID = keccak256("vault");
|
||||
bytes32 constant public LP_APP_ID = keccak256("liquidPledging");
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
import "@aragon/os/contracts/factory/DAOFactory.sol";
|
||||
import "./LPVault.sol";
|
||||
import "./LiquidPledging.sol";
|
||||
import "./LPConstants.sol";
|
||||
|
||||
contract LPFactory is LPConstants, DAOFactory {
|
||||
address public vaultBase;
|
||||
address public lpBase;
|
||||
|
||||
event DeployVault(address vault);
|
||||
event DeployLiquidPledging(address liquidPledging);
|
||||
|
||||
function LPFactory(address _vaultBase, address _lpBase) public DAOFactory(0) {
|
||||
require(_vaultBase != 0);
|
||||
require(_lpBase != 0);
|
||||
vaultBase = _vaultBase;
|
||||
lpBase = _lpBase;
|
||||
}
|
||||
|
||||
function newLP(address _root, address _escapeHatchDestination) external {
|
||||
Kernel kernel = newDAO(this);
|
||||
ACL acl = ACL(kernel.acl());
|
||||
|
||||
bytes32 appManagerRole = kernel.APP_MANAGER_ROLE();
|
||||
|
||||
acl.createPermission(this, address(kernel), appManagerRole, this);
|
||||
|
||||
LPVault v = LPVault(kernel.newAppInstance(VAULT_APP_ID, vaultBase));
|
||||
LiquidPledging lp = LiquidPledging(kernel.newAppInstance(LP_APP_ID, lpBase));
|
||||
v.initialize(address(lp), _escapeHatchDestination);
|
||||
lp.initialize(address(v), _escapeHatchDestination);
|
||||
|
||||
// register the lp instance w/ the kernel
|
||||
kernel.setApp(kernel.APP_ADDR_NAMESPACE(), LP_APP_ID, address(lp));
|
||||
|
||||
_setPermissions(_root, acl, kernel, v, lp);
|
||||
}
|
||||
|
||||
function _setPermissions(address _root, ACL acl, Kernel kernel, LPVault v, LiquidPledging lp) internal {
|
||||
bytes32 appManagerRole = kernel.APP_MANAGER_ROLE();
|
||||
bytes32 permRole = acl.CREATE_PERMISSIONS_ROLE();
|
||||
bytes32 hatchCallerRole = v.ESCAPE_HATCH_CALLER_ROLE();
|
||||
bytes32 pluginManagerRole = lp.PLUGIN_MANAGER_ROLE();
|
||||
|
||||
acl.createPermission(_root, address(v), hatchCallerRole, _root);
|
||||
acl.createPermission(_root, address(lp), hatchCallerRole, _root);
|
||||
acl.createPermission(_root, address(lp), pluginManagerRole, _root);
|
||||
// TODO: set pledgeAdminRole manager to 0x0? maybe it doesn't matter b/c it can be recreated by _root anyways
|
||||
|
||||
acl.grantPermission(_root, address(kernel), appManagerRole);
|
||||
acl.grantPermission(_root, address(acl), permRole);
|
||||
acl.revokePermission(this, address(kernel), appManagerRole);
|
||||
acl.revokePermission(this, address(acl), permRole);
|
||||
|
||||
acl.setPermissionManager(_root, address(kernel), appManagerRole);
|
||||
acl.setPermissionManager(_root, address(acl), permRole);
|
||||
|
||||
DeployVault(address(v));
|
||||
DeployLiquidPledging(address(lp));
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
pragma solidity ^0.4.11;
|
||||
pragma solidity ^0.4.18;
|
||||
|
||||
/*
|
||||
Copyright 2017, Jordi Baylina
|
||||
|
@ -18,49 +18,65 @@ pragma solidity ^0.4.11;
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// @title LPVault
|
||||
/// @author Jordi Baylina
|
||||
|
||||
/// @dev This contract holds ether securely for liquid pledging systems; for
|
||||
/// this iteration the funds will come often be escaped to the Giveth Multisig
|
||||
/// (safety precaution), but once fully tested and optimized this contract will
|
||||
/// be a safe place to store funds equipped with optional variable time delays
|
||||
/// to allow for an optional escapeHatch to be implemented in case of issues;
|
||||
/// future versions of this contract will be enabled for tokens
|
||||
import "./Owned.sol";// TODO IMPORT ESCAPABLE :-D...............................???????????????????????????/
|
||||
import "./EscapableApp.sol";
|
||||
import "./LiquidPledgingACLHelpers.sol";
|
||||
import "giveth-common-contracts/contracts/ERC20.sol";
|
||||
|
||||
/// @dev `LiquidPledging` is a basic interface to allow the `LPVault` contract
|
||||
/// to confirm and cancel payments in the `LiquidPledging` contract.
|
||||
contract LiquidPledging {
|
||||
contract ILiquidPledging {
|
||||
function confirmPayment(uint64 idPledge, uint amount) public;
|
||||
function cancelPayment(uint64 idPledge, uint amount) public;
|
||||
}
|
||||
|
||||
|
||||
/// @dev `LPVault` is a higher level contract built off of the `Owned`
|
||||
/// @dev `LPVault` is a higher level contract built off of the `Escapable`
|
||||
/// contract that holds funds for the liquid pledging system.
|
||||
contract LPVault is Owned {// TODO NEEDS TO BE ESCAPABLE!!! AND WE NEED TO ADD A `WITHDRAW PARTIAL` FUNCTION????????????????
|
||||
contract LPVault is EscapableApp, LiquidPledgingACLHelpers {
|
||||
|
||||
LiquidPledging public liquidPledging; // LiquidPledging contract's address
|
||||
bool public autoPay; // If false, payments will take 2 txs to be completed
|
||||
bytes32 constant public CONFIRM_PAYMENT_ROLE = keccak256("CONFIRM_PAYMENT_ROLE");
|
||||
bytes32 constant public CANCEL_PAYMENT_ROLE = keccak256("CANCEL_PAYMENT_ROLE");
|
||||
bytes32 constant public SET_AUTOPAY_ROLE = keccak256("SET_AUTOPAY_ROLE");
|
||||
|
||||
event AutoPaySet(bool autoPay);
|
||||
event EscapeFundsCalled(address token, uint amount);
|
||||
event ConfirmPayment(uint indexed idPayment, bytes32 indexed ref);
|
||||
event CancelPayment(uint indexed idPayment, bytes32 indexed ref);
|
||||
event AuthorizePayment(
|
||||
uint indexed idPayment,
|
||||
bytes32 indexed ref,
|
||||
address indexed dest,
|
||||
address token,
|
||||
uint amount
|
||||
);
|
||||
|
||||
enum PaymentStatus {
|
||||
Pending, // When the payment is awaiting confirmation
|
||||
Paid, // When the payment has been sent
|
||||
Canceled // When the payment will never be sent
|
||||
}
|
||||
|
||||
/// @dev `Payment` is a public structure that describes the details of
|
||||
/// each payment the `ref` param makes it easy to track the movements of
|
||||
/// funds transparently by its connection to other `Payment` structs
|
||||
struct Payment {
|
||||
PaymentStatus state; // Pending, Paid or Canceled
|
||||
bytes32 ref; // an input that references details from other contracts
|
||||
address dest; // recipient of the ETH
|
||||
PaymentStatus state; // Pending, Paid or Canceled
|
||||
address token;
|
||||
uint amount; // amount of ETH (in wei) to be sent
|
||||
}
|
||||
|
||||
bool public autoPay; // If false, payments will take 2 txs to be completed
|
||||
|
||||
// @dev An array that contains all the payments for this LPVault
|
||||
Payment[] public payments;
|
||||
ILiquidPledging public liquidPledging;
|
||||
|
||||
/// @dev The attached `LiquidPledging` contract is the only address that can
|
||||
/// call a function with this modifier
|
||||
|
@ -68,23 +84,25 @@ contract LPVault is Owned {// TODO NEEDS TO BE ESCAPABLE!!! AND WE NEED TO ADD A
|
|||
require(msg.sender == address(liquidPledging));
|
||||
_;
|
||||
}
|
||||
/// @dev Used for testing... SHOULD BE REMOVED FOR MAINNET LAUNCH OR MAYBE NOT????????????????????....................
|
||||
function VaultMock() public pure {
|
||||
|
||||
}
|
||||
/// @dev The fall back function allows ETH to be deposited into the LPVault
|
||||
/// through a simple send
|
||||
function () public payable {
|
||||
|
||||
function LPVault(address _escapeHatchDestination) EscapableApp(_escapeHatchDestination) public {
|
||||
}
|
||||
|
||||
/// @notice `onlyOwner` used to attach a specific liquidPledging instance
|
||||
/// to this LPvault; keep in mind that once a liquidPledging contract is
|
||||
/// attached it cannot be undone, this vault will be forever connected
|
||||
/// @param _newLiquidPledging A full liquid pledging contract
|
||||
function setLiquidPledging(address _newLiquidPledging) public onlyOwner {
|
||||
require(address(liquidPledging) == 0x0);
|
||||
liquidPledging = LiquidPledging(_newLiquidPledging);
|
||||
function initialize(address _escapeHatchDestination) onlyInit public {
|
||||
require(false); // overload the EscapableApp
|
||||
_escapeHatchDestination;
|
||||
}
|
||||
|
||||
/// @param _liquidPledging
|
||||
/// @param _escapeHatchDestination The address of a safe location (usu a
|
||||
/// Multisig) to send the ether held in this contract; if a neutral address
|
||||
/// is required, the WHG Multisig is an option:
|
||||
/// 0x8Ff920020c8AD673661c8117f2855C384758C572
|
||||
function initialize(address _liquidPledging, address _escapeHatchDestination) onlyInit external {
|
||||
super.initialize(_escapeHatchDestination);
|
||||
|
||||
require(_liquidPledging != 0x0);
|
||||
liquidPledging = ILiquidPledging(_liquidPledging);
|
||||
}
|
||||
|
||||
/// @notice Used to decentralize, toggles whether the LPVault will
|
||||
|
@ -92,13 +110,12 @@ contract LPVault is Owned {// TODO NEEDS TO BE ESCAPABLE!!! AND WE NEED TO ADD A
|
|||
/// @param _automatic If true, payments will confirm instantly, if false
|
||||
/// the training wheels are put on and the owner must manually approve
|
||||
/// every payment
|
||||
function setAutopay(bool _automatic) public onlyOwner {
|
||||
function setAutopay(bool _automatic) external authP(SET_AUTOPAY_ROLE, arr(_automatic)) {
|
||||
autoPay = _automatic;
|
||||
AutoPaySet();
|
||||
AutoPaySet(autoPay);
|
||||
}
|
||||
|
||||
/// @notice `onlyLiquidPledging` authorizes payments from this contract, if
|
||||
/// `autoPay == true` the transfer happens automatically `else` the `owner`
|
||||
/// @notice If `autoPay == true` the transfer happens automatically `else` the `owner`
|
||||
/// must call `confirmPayment()` for a transfer to occur (training wheels);
|
||||
/// either way, a new payment is added to `payments[]`
|
||||
/// @param _ref References the payment will normally be the pledgeID
|
||||
|
@ -108,18 +125,22 @@ contract LPVault is Owned {// TODO NEEDS TO BE ESCAPABLE!!! AND WE NEED TO ADD A
|
|||
function authorizePayment(
|
||||
bytes32 _ref,
|
||||
address _dest,
|
||||
uint _amount ) public onlyLiquidPledging returns (uint) {
|
||||
address _token,
|
||||
uint _amount
|
||||
) external onlyLiquidPledging returns (uint)
|
||||
{
|
||||
uint idPayment = payments.length;
|
||||
payments.length ++;
|
||||
payments[idPayment].state = PaymentStatus.Pending;
|
||||
payments[idPayment].ref = _ref;
|
||||
payments[idPayment].dest = _dest;
|
||||
payments[idPayment].token = _token;
|
||||
payments[idPayment].amount = _amount;
|
||||
|
||||
AuthorizePayment(idPayment, _ref, _dest, _amount);
|
||||
AuthorizePayment(idPayment, _ref, _dest, _token, _amount);
|
||||
|
||||
if (autoPay) {
|
||||
doConfirmPayment(idPayment);
|
||||
_doConfirmPayment(idPayment);
|
||||
}
|
||||
|
||||
return idPayment;
|
||||
|
@ -130,14 +151,56 @@ contract LPVault is Owned {// TODO NEEDS TO BE ESCAPABLE!!! AND WE NEED TO ADD A
|
|||
/// this is generally used when `autopay` is `false` after a payment has
|
||||
/// has been authorized
|
||||
/// @param _idPayment Array lookup for the payment.
|
||||
function confirmPayment(uint _idPayment) public onlyOwner {
|
||||
doConfirmPayment(_idPayment);
|
||||
function confirmPayment(uint _idPayment) public {
|
||||
Payment storage p = payments[_idPayment];
|
||||
require(canPerform(msg.sender, CONFIRM_PAYMENT_ROLE, arr(_idPayment, p.amount)));
|
||||
_doConfirmPayment(_idPayment);
|
||||
}
|
||||
|
||||
/// @notice When `autopay` is `false` and after a payment has been authorized
|
||||
/// to allow the owner to cancel a payment instead of confirming it.
|
||||
/// @param _idPayment Array lookup for the payment.
|
||||
function cancelPayment(uint _idPayment) external {
|
||||
_doCancelPayment(_idPayment);
|
||||
}
|
||||
|
||||
/// @notice `onlyOwner` An efficient way to confirm multiple payments
|
||||
/// @param _idPayments An array of multiple payment ids
|
||||
function multiConfirm(uint[] _idPayments) external {
|
||||
for (uint i = 0; i < _idPayments.length; i++) {
|
||||
confirmPayment(_idPayments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice `onlyOwner` An efficient way to cancel multiple payments
|
||||
/// @param _idPayments An array of multiple payment ids
|
||||
function multiCancel(uint[] _idPayments) external {
|
||||
for (uint i = 0; i < _idPayments.length; i++) {
|
||||
_doCancelPayment(_idPayments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transfer tokens to the escapeHatchDestination.
|
||||
/// Used as a safety mechanism to prevent the vault from holding too much value
|
||||
/// before being thoroughly battle-tested.
|
||||
/// @param _token to transfer
|
||||
/// @param _amount to transfer
|
||||
function escapeFunds(address _token, uint _amount) external authP(ESCAPE_HATCH_CALLER_ROLE, arr(_token)) {
|
||||
require(_token != 0x0);
|
||||
ERC20 token = ERC20(_token);
|
||||
require(token.transfer(escapeHatchDestination, _amount));
|
||||
EscapeFundsCalled(_token, _amount);
|
||||
}
|
||||
|
||||
/// @return The total number of payments that have ever been authorized
|
||||
function nPayments() external view returns (uint) {
|
||||
return payments.length;
|
||||
}
|
||||
|
||||
/// @notice Transfers ETH according to the data held within the specified
|
||||
/// payment id (internal function)
|
||||
/// @param _idPayment id number for the payment about to be fulfilled
|
||||
function doConfirmPayment(uint _idPayment) internal {
|
||||
function _doConfirmPayment(uint _idPayment) internal {
|
||||
require(_idPayment < payments.length);
|
||||
Payment storage p = payments[_idPayment];
|
||||
require(p.state == PaymentStatus.Pending);
|
||||
|
@ -145,21 +208,15 @@ contract LPVault is Owned {// TODO NEEDS TO BE ESCAPABLE!!! AND WE NEED TO ADD A
|
|||
p.state = PaymentStatus.Paid;
|
||||
liquidPledging.confirmPayment(uint64(p.ref), p.amount);
|
||||
|
||||
p.dest.transfer(p.amount); // Transfers ETH denominated in wei
|
||||
ERC20 token = ERC20(p.token);
|
||||
require(token.transfer(p.dest, p.amount)); // Transfers token to dest
|
||||
|
||||
ConfirmPayment(_idPayment);
|
||||
}
|
||||
|
||||
/// @notice When `autopay` is `false` and after a payment has been authorized
|
||||
/// to allow the owner to cancel a payment instead of confirming it.
|
||||
/// @param _idPayment Array lookup for the payment.
|
||||
function cancelPayment(uint _idPayment) public onlyOwner {
|
||||
doCancelPayment(_idPayment);
|
||||
ConfirmPayment(_idPayment, p.ref);
|
||||
}
|
||||
|
||||
/// @notice Cancels a pending payment (internal function)
|
||||
/// @param _idPayment id number for the payment
|
||||
function doCancelPayment(uint _idPayment) internal {
|
||||
function _doCancelPayment(uint _idPayment) internal authP(CANCEL_PAYMENT_ROLE, arr(_idPayment)) {
|
||||
require(_idPayment < payments.length);
|
||||
Payment storage p = payments[_idPayment];
|
||||
require(p.state == PaymentStatus.Pending);
|
||||
|
@ -168,38 +225,6 @@ contract LPVault is Owned {// TODO NEEDS TO BE ESCAPABLE!!! AND WE NEED TO ADD A
|
|||
|
||||
liquidPledging.cancelPayment(uint64(p.ref), p.amount);
|
||||
|
||||
CancelPayment(_idPayment);
|
||||
|
||||
CancelPayment(_idPayment, p.ref);
|
||||
}
|
||||
|
||||
/// @notice `onlyOwner` An efficient way to confirm multiple payments
|
||||
/// @param _idPayments An array of multiple payment ids
|
||||
function multiConfirm(uint[] _idPayments) public onlyOwner {
|
||||
for (uint i = 0; i < _idPayments.length; i++) {
|
||||
doConfirmPayment(_idPayments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice `onlyOwner` An efficient way to cancel multiple payments
|
||||
/// @param _idPayments An array of multiple payment ids
|
||||
function multiCancel(uint[] _idPayments) public onlyOwner {
|
||||
for (uint i = 0; i < _idPayments.length; i++) {
|
||||
doCancelPayment(_idPayments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @return The total number of payments that have ever been authorized
|
||||
function nPayments() constant public returns (uint) {
|
||||
return payments.length;
|
||||
}
|
||||
|
||||
event AutoPaySet();
|
||||
event ConfirmPayment(uint indexed idPayment);
|
||||
event CancelPayment(uint indexed idPayment);
|
||||
event AuthorizePayment(
|
||||
uint indexed idPayment,
|
||||
bytes32 indexed ref,
|
||||
address indexed dest,
|
||||
uint amount
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
pragma solidity ^0.4.11;
|
||||
pragma solidity ^0.4.18;
|
||||
|
||||
/*
|
||||
Copyright 2017, Jordi Baylina
|
||||
Contributors: Adrià Massanet <adria@codecontext.io>, RJ Ewing, Griff
|
||||
Green, Arthur Lunn
|
||||
Copyright 2017, Jordi Baylina, RJ Ewing
|
||||
Contributors: Adrià Massanet <adria@codecontext.io>, 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
|
||||
|
@ -21,23 +21,28 @@ pragma solidity ^0.4.11;
|
|||
|
||||
import "./LiquidPledgingBase.sol";
|
||||
|
||||
/// @dev `LiquidPleding` allows for liquid pledging through the use of
|
||||
/// @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 {
|
||||
|
||||
function LiquidPledging(address _escapeHatchDestination) EscapableApp(_escapeHatchDestination) public {
|
||||
}
|
||||
|
||||
//////
|
||||
// Constructor
|
||||
//////
|
||||
function addGiverAndDonate(uint64 idReceiver, address token, uint amount)
|
||||
public
|
||||
{
|
||||
addGiverAndDonate(idReceiver, msg.sender, token, amount);
|
||||
}
|
||||
|
||||
/// @notice Basic constructor for LiquidPleding, also calls the
|
||||
/// LiquidPledgingBase contract
|
||||
/// @dev This constructor also calls the constructor
|
||||
/// for `LiquidPledgingBase`
|
||||
/// @param _vault The vault where ETH backing this pledge is stored
|
||||
function LiquidPledging(address _vault) LiquidPledgingBase(_vault) {
|
||||
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;
|
||||
|
@ -45,38 +50,40 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
/// 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; if 0, a new id is created
|
||||
/// @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
|
||||
function donate(uint64 idGiver, uint64 idReceiver) payable {
|
||||
if (idGiver == 0) {
|
||||
|
||||
// default to a 3 day (259200 seconds) commitTime
|
||||
idGiver = addGiver("", "", 259200, ILiquidPledgingPlugin(0x0));
|
||||
}
|
||||
|
||||
PledgeAdmin storage sender = findAdmin(idGiver);
|
||||
checkAdminOwner(sender);
|
||||
require(sender.adminType == PledgeAdminType.Giver);
|
||||
uint amount = msg.value;
|
||||
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);
|
||||
vault.transfer(amount); // Sends the `msg.value` (in wei) to the `vault`
|
||||
uint64 idPledge = findOrCreatePledge(
|
||||
require(token != 0x0);
|
||||
|
||||
PledgeAdmin storage sender = _findAdmin(idGiver);
|
||||
require(sender.adminType == PledgeAdminType.Giver);
|
||||
|
||||
// TODO should this be done at the end of this function?
|
||||
// what re-entrancy issues are there if this is done here?
|
||||
// if done at the end of the function, will that affect plugins?
|
||||
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;
|
||||
|
||||
Pledge storage nTo = findPledge(idPledge);
|
||||
nTo.amount += amount;
|
||||
Transfer(0, idPledge, amount);
|
||||
|
||||
Transfer(0, idPledge, amount); // An event
|
||||
|
||||
transfer(idGiver, idPledge, amount, idReceiver); // LP accounting
|
||||
_transfer(idGiver, idPledge, amount, idReceiver);
|
||||
}
|
||||
|
||||
/// @notice Transfers amounts between pledges for internal accounting
|
||||
|
@ -93,101 +100,10 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
uint64 idPledge,
|
||||
uint amount,
|
||||
uint64 idReceiver
|
||||
){
|
||||
|
||||
idPledge = normalizePledge(idPledge);
|
||||
|
||||
Pledge storage p = findPledge(idPledge);
|
||||
PledgeAdmin storage receiver = findAdmin(idReceiver);
|
||||
PledgeAdmin storage sender = findAdmin(idSender);
|
||||
|
||||
checkAdminOwner(sender);
|
||||
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);
|
||||
} else if (receiver.adminType == PledgeAdminType.Project) {
|
||||
transferOwnershipToProject(idPledge, amount, idReceiver);
|
||||
} else if (receiver.adminType == PledgeAdminType.Delegate) {
|
||||
idPledge = undelegate(
|
||||
idPledge,
|
||||
amount,
|
||||
p.delegationChain.length
|
||||
);
|
||||
appendDelegate(idPledge, amount, idReceiver);
|
||||
} else {
|
||||
// This should never be reached as the reciever.adminType
|
||||
// should always be either a Giver, Project, or Delegate
|
||||
assert(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 pldege
|
||||
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
|
||||
if (receiverDIdx == NOTFOUND) {
|
||||
idPledge = undelegate(
|
||||
idPledge,
|
||||
amount,
|
||||
p.delegationChain.length - senderDIdx - 1
|
||||
);
|
||||
appendDelegate(idPledge, amount, idReceiver);
|
||||
|
||||
// And part of the delegationChain and 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
|
||||
} else if (receiverDIdx > senderDIdx) {
|
||||
idPledge = undelegate(
|
||||
idPledge,
|
||||
amount,
|
||||
p.delegationChain.length - senderDIdx - 1
|
||||
);
|
||||
appendDelegate(idPledge, amount, idReceiver);
|
||||
|
||||
// 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
|
||||
} else if (receiverDIdx <= senderDIdx) {//TODO Check for Game Theory issues (from Arthur) this allows the sender to sort of go komakosi and remove himself and the delegates between himself and the receiver... should this authority be allowed?
|
||||
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
|
||||
) public
|
||||
{
|
||||
_checkAdminOwner(idSender);
|
||||
_transfer(idSender, idPledge, amount, idReceiver);
|
||||
}
|
||||
|
||||
/// @notice Authorizes a payment be made from the `vault` can be used by the
|
||||
|
@ -195,77 +111,81 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
/// 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) {
|
||||
function withdraw(uint64 idPledge, uint amount) public {
|
||||
idPledge = normalizePledge(idPledge); // Updates pledge info
|
||||
Pledge storage p = findPledge(idPledge);
|
||||
require(p.pledgeState == PledgeState.Pledged);
|
||||
PledgeAdmin storage owner = findAdmin(p.owner);
|
||||
checkAdminOwner(owner);
|
||||
|
||||
uint64 idNewPledge = findOrCreatePledge(
|
||||
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);
|
||||
_doTransfer(idPledge, idNewPledge, amount);
|
||||
|
||||
vault.authorizePayment(bytes32(idNewPledge), owner.addr, 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) onlyVault {
|
||||
Pledge storage p = findPledge(idPledge);
|
||||
function confirmPayment(uint64 idPledge, uint amount) public onlyVault {
|
||||
Pledge storage p = _findPledge(idPledge);
|
||||
|
||||
require(p.pledgeState == PledgeState.Paying);
|
||||
|
||||
uint64 idNewPledge = findOrCreatePledge(
|
||||
uint64 idNewPledge = _findOrCreatePledge(
|
||||
p.owner,
|
||||
p.delegationChain,
|
||||
0,
|
||||
0,
|
||||
p.oldPledge,
|
||||
p.token,
|
||||
PledgeState.Paid
|
||||
);
|
||||
|
||||
doTransfer(idPledge, idNewPledge, amount);
|
||||
_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) onlyVault {
|
||||
Pledge storage p = findPledge(idPledge);
|
||||
function cancelPayment(uint64 idPledge, uint amount) public onlyVault {
|
||||
Pledge storage p = _findPledge(idPledge);
|
||||
|
||||
require(p.pledgeState == PledgeState.Paying); //TODO change to revert????????????????????????????
|
||||
require(p.pledgeState == PledgeState.Paying);
|
||||
|
||||
// When a payment is canceled, never is assigned to a project.
|
||||
uint64 oldPledge = findOrCreatePledge(
|
||||
uint64 idOldPledge = _findOrCreatePledge(
|
||||
p.owner,
|
||||
p.delegationChain,
|
||||
0,
|
||||
0,
|
||||
p.oldPledge,
|
||||
p.token,
|
||||
PledgeState.Pledged
|
||||
);
|
||||
|
||||
oldPledge = normalizePledge(oldPledge);
|
||||
idOldPledge = normalizePledge(idOldPledge);
|
||||
|
||||
doTransfer(idPledge, oldPledge, amount);
|
||||
_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) {
|
||||
PledgeAdmin storage project = findAdmin(idProject);
|
||||
checkAdminOwner(project);
|
||||
function cancelProject(uint64 idProject) public {
|
||||
PledgeAdmin storage project = _findAdmin(idProject);
|
||||
_checkAdminOwner(idProject);
|
||||
project.canceled = true;
|
||||
|
||||
CancelProject(idProject);
|
||||
|
@ -276,17 +196,16 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
/// @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) {
|
||||
function cancelPledge(uint64 idPledge, uint amount) public {
|
||||
idPledge = normalizePledge(idPledge);
|
||||
|
||||
Pledge storage p = findPledge(idPledge);
|
||||
Pledge storage p = _findPledge(idPledge);
|
||||
require(p.oldPledge != 0);
|
||||
require(p.pledgeState == PledgeState.Pledged);
|
||||
_checkAdminOwner(p.owner);
|
||||
|
||||
PledgeAdmin storage m = findAdmin(p.owner);
|
||||
checkAdminOwner(m);
|
||||
|
||||
uint64 oldPledge = getOldestPledgeNotCanceled(p.oldPledge);
|
||||
doTransfer(idPledge, oldPledge, amount);
|
||||
uint64 oldPledge = _getOldestPledgeNotCanceled(p.oldPledge);
|
||||
_doTransfer(idPledge, oldPledge, amount);
|
||||
}
|
||||
|
||||
|
||||
|
@ -315,9 +234,10 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
uint64 idSender,
|
||||
uint[] pledgesAmounts,
|
||||
uint64 idReceiver
|
||||
) {
|
||||
) public
|
||||
{
|
||||
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
||||
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
||||
uint64 idPledge = uint64(pledgesAmounts[i] & (D64-1));
|
||||
uint amount = pledgesAmounts[i] / D64;
|
||||
|
||||
transfer(idSender, idPledge, amount, idReceiver);
|
||||
|
@ -329,9 +249,9 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
/// @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) {
|
||||
function mWithdraw(uint[] pledgesAmounts) public {
|
||||
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
||||
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
||||
uint64 idPledge = uint64(pledgesAmounts[i] & (D64-1));
|
||||
uint amount = pledgesAmounts[i] / D64;
|
||||
|
||||
withdraw(idPledge, amount);
|
||||
|
@ -342,9 +262,9 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
/// efficiently
|
||||
/// @param pledgesAmounts An array of pledge amounts and IDs which are extrapolated
|
||||
/// using the D64 bitmask
|
||||
function mConfirmPayment(uint[] pledgesAmounts) {
|
||||
function mConfirmPayment(uint[] pledgesAmounts) public {
|
||||
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
||||
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
||||
uint64 idPledge = uint64(pledgesAmounts[i] & (D64-1));
|
||||
uint amount = pledgesAmounts[i] / D64;
|
||||
|
||||
confirmPayment(idPledge, amount);
|
||||
|
@ -355,9 +275,9 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
/// efficiently
|
||||
/// @param pledgesAmounts An array of pledge amounts and IDs which are extrapolated
|
||||
/// using the D64 bitmask
|
||||
function mCancelPayment(uint[] pledgesAmounts) {
|
||||
function mCancelPayment(uint[] pledgesAmounts) public {
|
||||
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
||||
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
||||
uint64 idPledge = uint64(pledgesAmounts[i] & (D64-1));
|
||||
uint amount = pledgesAmounts[i] / D64;
|
||||
|
||||
cancelPayment(idPledge, amount);
|
||||
|
@ -367,416 +287,9 @@ contract LiquidPledging is LiquidPledgingBase {
|
|||
/// @notice `mNormalizePledge` allows for multiple pledges to be
|
||||
/// normalized efficiently
|
||||
/// @param pledges An array of pledge IDs
|
||||
function mNormalizePledge(uint64[] pledges) {
|
||||
function mNormalizePledge(uint64[] pledges) public {
|
||||
for (uint i = 0; i < pledges.length; i++ ) {
|
||||
normalizePledge( pledges[i] );
|
||||
normalizePledge(pledges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
////////
|
||||
// Private methods
|
||||
///////
|
||||
|
||||
/// @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 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,
|
||||
PledgeState.Pledged
|
||||
);
|
||||
uint64 toPledge = findOrCreatePledge(
|
||||
idReceiver, // Set the new owner
|
||||
new uint64[](0), // clear the delegation chain
|
||||
0,
|
||||
0,
|
||||
oldPledge,
|
||||
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 Id of the pledge to be transfered.
|
||||
/// @param amount Quantity of value that's being transfered
|
||||
/// @param idReceiver The new owner of the pledge
|
||||
function transferOwnershipToGiver(
|
||||
uint64 idPledge,
|
||||
uint amount,
|
||||
uint64 idReceiver
|
||||
) internal {
|
||||
uint64 toPledge = findOrCreatePledge(
|
||||
idReceiver,
|
||||
new uint64[](0),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
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 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,
|
||||
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 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)
|
||||
{
|
||||
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];
|
||||
}
|
||||
uint64 toPledge = findOrCreatePledge(
|
||||
p.owner,
|
||||
newDelegationChain,
|
||||
0,
|
||||
0,
|
||||
p.oldPledge,
|
||||
PledgeState.Pledged
|
||||
);
|
||||
doTransfer(idPledge, toPledge, amount);
|
||||
|
||||
return toPledge;
|
||||
}
|
||||
|
||||
/// @notice `proposeAssignProject` proposes the assignment of a pledge
|
||||
/// to a specific project.
|
||||
/// @dev This function should potentially be named more specifically.
|
||||
/// @param idPledge 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_SUBPROJECT_LEVEL);
|
||||
require(!isProjectCanceled(idReceiver));
|
||||
|
||||
uint64 toPledge = findOrCreatePledge(
|
||||
p.owner,
|
||||
p.delegationChain,
|
||||
idReceiver,
|
||||
uint64(getTime() + maxCommitTime(p)),
|
||||
p.oldPledge,
|
||||
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 from which value will be transfered.
|
||||
/// @param to This is the Id that value will be transfered to.
|
||||
/// @param _amount The amount of value that will be transfered.
|
||||
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 nFrom = findPledge(from);
|
||||
Pledge storage nTo = findPledge(to);
|
||||
require(nFrom.amount >= amount);
|
||||
nFrom.amount -= amount;
|
||||
nTo.amount += amount;
|
||||
|
||||
Transfer(from, to, amount);
|
||||
callPlugins(false, from, to, amount);
|
||||
}
|
||||
|
||||
/// @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) 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,
|
||||
PledgeState.Pledged
|
||||
);
|
||||
uint64 toPledge = findOrCreatePledge(
|
||||
p.intendedProject,
|
||||
new uint64[](0),
|
||||
0,
|
||||
0,
|
||||
oldPledge,
|
||||
PledgeState.Pledged
|
||||
);
|
||||
doTransfer(idPledge, toPledge, p.amount);
|
||||
idPledge = toPledge;
|
||||
p = findPledge(idPledge);
|
||||
}
|
||||
|
||||
toPledge = getOldestPledgeNotCanceled(idPledge);// TODO toPledge is pledge defined
|
||||
if (toPledge != idPledge) {
|
||||
doTransfer(idPledge, toPledge, p.amount);
|
||||
}
|
||||
|
||||
return toPledge;
|
||||
}
|
||||
|
||||
/////////////
|
||||
// Plugins
|
||||
/////////////
|
||||
|
||||
/// @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,
|
||||
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 seperate functions called in the plugin.
|
||||
// One is called before the transfer and one after
|
||||
if (before) {
|
||||
newAmount = admin.plugin.beforeTransfer(
|
||||
adminId,
|
||||
fromPledge,
|
||||
toPledge,
|
||||
context,
|
||||
amount
|
||||
);
|
||||
require(newAmount <= allowedAmount);
|
||||
allowedAmount = newAmount;
|
||||
} else {
|
||||
admin.plugin.afterTransfer(
|
||||
adminId,
|
||||
fromPledge,
|
||||
toPledge,
|
||||
context,
|
||||
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 `idPledge` 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,
|
||||
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,
|
||||
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,
|
||||
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 occuring
|
||||
/// before or after a transfer.
|
||||
/// @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 callPlugins(
|
||||
bool before,
|
||||
uint64 fromPledge,
|
||||
uint64 toPledge,
|
||||
uint amount
|
||||
) internal returns (uint allowedAmount) {
|
||||
allowedAmount = amount;
|
||||
|
||||
// Call the pledges plugins in the transfer context
|
||||
allowedAmount = callPluginsPledge(
|
||||
before,
|
||||
fromPledge,
|
||||
fromPledge,
|
||||
toPledge,
|
||||
allowedAmount
|
||||
);
|
||||
|
||||
// Call the pledges 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 returns (uint) {
|
||||
return now;
|
||||
}
|
||||
|
||||
// Event Delcerations
|
||||
event Transfer(uint64 indexed from, uint64 indexed to, uint amount);
|
||||
event CancelProject(uint64 indexed idProject);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
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 diff suppressed because it is too large
Load Diff
|
@ -26,16 +26,19 @@ contract LiquidPledgingMock is LiquidPledging {
|
|||
|
||||
uint public mock_time;
|
||||
|
||||
function LiquidPledgingMock(address _escapeHatchDestination) LiquidPledging(_escapeHatchDestination) public {
|
||||
}
|
||||
|
||||
/// @dev `LiquidPledgingMock` creates a standard `LiquidPledging`
|
||||
/// instance and sets the mocked time to the current blocktime.
|
||||
/// @param _vault The vault where ETH backing this pledge is stored
|
||||
function LiquidPledgingMock(address _vault) LiquidPledging(_vault) {
|
||||
function initialize(address _vault, address _escapeHatchDestination) onlyInit public {
|
||||
super.initialize(_vault, _escapeHatchDestination);
|
||||
mock_time = now;
|
||||
}
|
||||
|
||||
/// @dev `getTime` is a basic getter function for
|
||||
/// the mock_time parameter
|
||||
function getTime() internal returns (uint) {
|
||||
function _getTime() internal view returns (uint) {
|
||||
return mock_time;
|
||||
}
|
||||
|
||||
|
@ -43,7 +46,7 @@ contract LiquidPledgingMock is LiquidPledging {
|
|||
/// the mock_time parameter
|
||||
/// @param _t This is the value to which the mocked time
|
||||
/// will be set.
|
||||
function setMockedTime(uint _t) {
|
||||
function setMockedTime(uint _t) public {
|
||||
mock_time = _t;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
/*
|
||||
Copyright 2017, Jordi Baylina, RJ Ewing
|
||||
Contributors: Adrià Massanet <adria@codecontext.io>, 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
||||
import "./LiquidPledgingStorage.sol";
|
||||
import "./LiquidPledgingACLHelpers.sol";
|
||||
|
||||
contract LiquidPledgingPlugins is AragonApp, LiquidPledgingStorage, LiquidPledgingACLHelpers {
|
||||
|
||||
bytes32 constant public PLUGIN_MANAGER_ROLE = keccak256("PLUGIN_MANAGER_ROLE");
|
||||
|
||||
function addValidPluginInstance(address addr) auth(PLUGIN_MANAGER_ROLE) external {
|
||||
pluginInstanceWhitelist[addr] = true;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
function removeValidPluginContract(bytes32 contractHash) external authP(PLUGIN_MANAGER_ROLE, arr(contractHash)) {
|
||||
pluginContractWhitelist[contractHash] = false;
|
||||
}
|
||||
|
||||
function removeValidPluginInstance(address addr) external authP(PLUGIN_MANAGER_ROLE, arr(addr)) {
|
||||
pluginInstanceWhitelist[addr] = false;
|
||||
}
|
||||
|
||||
function useWhitelist(bool useWhitelist) external auth(PLUGIN_MANAGER_ROLE) {
|
||||
whitelistDisabled = !useWhitelist;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
function getCodeHash(address addr) public view returns(bytes32) {
|
||||
bytes memory o_code;
|
||||
assembly {
|
||||
// retrieve the size of the code, this needs assembly
|
||||
let size := extcodesize(addr)
|
||||
// allocate output byte array - this could also be done without assembly
|
||||
// by using o_code = new bytes(size)
|
||||
o_code := mload(0x40)
|
||||
mstore(o_code, size) // store length in memory
|
||||
// actually retrieve the code, this needs assembly
|
||||
extcodecopy(addr, add(o_code, 0x20), 0, size)
|
||||
}
|
||||
return keccak256(o_code);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
import "./ILiquidPledgingPlugin.sol";
|
||||
|
||||
/// @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. I'm not sure if this is necessary
|
||||
// but b/c of multiple inheritance used in lp, better safe then sorry.
|
||||
// especially since it is free
|
||||
uint[50] private storageOffset;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
pragma solidity ^0.4.11;
|
||||
|
||||
|
||||
/// @dev `Owned` is a base level contract that assigns an `owner` that can be
|
||||
/// later changed
|
||||
contract Owned {
|
||||
|
||||
/// @dev `owner` is the only address that can call a function with this
|
||||
/// modifier
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner);
|
||||
_;
|
||||
}
|
||||
|
||||
address public owner;
|
||||
|
||||
|
||||
/// @notice The Constructor assigns the account deploying the contract to be
|
||||
/// the `owner`
|
||||
function Owned() public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
address public newOwner;
|
||||
|
||||
/// @notice `owner` can step down and assign some other address to this role
|
||||
/// but after this function is called the current owner still has ownership
|
||||
/// powers in this contract; change of ownership is a 2 step process
|
||||
/// @param _newOwner The address of the new owner. A simple contract with
|
||||
/// the ability to accept ownership but the inability to do anything else
|
||||
/// can be used to create an unowned contract to achieve decentralization
|
||||
function changeOwner(address _newOwner) public onlyOwner {
|
||||
newOwner = _newOwner;
|
||||
}
|
||||
|
||||
/// @notice `newOwner` can accept ownership over this contract
|
||||
function acceptOwnership() public {
|
||||
require(msg.sender == newOwner);
|
||||
owner = newOwner;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
/*
|
||||
Copyright 2017, Jordi Baylina, RJ Ewing
|
||||
Contributors: Adrià Massanet <adria@codecontext.io>, 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import "./LiquidPledgingPlugins.sol";
|
||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: is there an issue w/ allowing anyone to create a giver on behalf of another addy?
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
/*
|
||||
Copyright 2017, Jordi Baylina, RJ Ewing
|
||||
Contributors: Adrià Massanet <adria@codecontext.io>, 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
||||
import "./LiquidPledgingStorage.sol";
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
/**
|
||||
* WARNING: This token is for testing purposes only
|
||||
* and has been modified from the original version: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/StandardToken.sol
|
||||
*
|
||||
* @title Standard ERC20 token
|
||||
*
|
||||
* @dev Implementation of the basic standard token.
|
||||
* @dev https://github.com/ethereum/EIPs/issues/20
|
||||
* @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
|
||||
*/
|
||||
contract StandardToken {
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
|
||||
mapping (address => mapping (address => uint256)) internal allowed;
|
||||
mapping(address => uint256) balances;
|
||||
|
||||
uint256 totalSupply_;
|
||||
address owner;
|
||||
|
||||
function StandardToken() public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev total number of tokens in existence
|
||||
*/
|
||||
function totalSupply() public view returns (uint256) {
|
||||
return totalSupply_;
|
||||
}
|
||||
|
||||
function mint(address _to, uint _value) public onlyOwner {
|
||||
totalSupply_ += _value;
|
||||
balances[_to] += _value;
|
||||
Transfer(0, _to, _value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev transfer token for a specified address
|
||||
* @param _to The address to transfer to.
|
||||
* @param _value The amount to be transferred.
|
||||
*/
|
||||
function transfer(address _to, uint256 _value) public returns (bool) {
|
||||
require(_to != address(0));
|
||||
require(_value <= balances[msg.sender]);
|
||||
|
||||
// SafeMath.sub will throw if there is not enough balance.
|
||||
balances[msg.sender] = balances[msg.sender] - _value;
|
||||
balances[_to] = balances[_to] + _value;
|
||||
Transfer(msg.sender, _to, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the balance of the specified address.
|
||||
* @param _owner The address to query the the balance of.
|
||||
* @return An uint256 representing the amount owned by the passed address.
|
||||
*/
|
||||
function balanceOf(address _owner) public view returns (uint256 balance) {
|
||||
return balances[_owner];
|
||||
}
|
||||
/**
|
||||
* @dev Transfer tokens from one address to another
|
||||
* @param _from address The address which you want to send tokens from
|
||||
* @param _to address The address which you want to transfer to
|
||||
* @param _value uint256 the amount of tokens to be transferred
|
||||
*/
|
||||
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
|
||||
require(_to != address(0));
|
||||
require(_value <= balances[_from]);
|
||||
require(_value <= allowed[_from][msg.sender]);
|
||||
|
||||
balances[_from] = balances[_from] - _value;
|
||||
balances[_to] = balances[_to] + _value;
|
||||
allowed[_from][msg.sender] = allowed[_from][msg.sender] - _value;
|
||||
Transfer(_from, _to, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
|
||||
*
|
||||
* Beware that changing an allowance with this method brings the risk that someone may use both the old
|
||||
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
|
||||
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
|
||||
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
|
||||
* @param _spender The address which will spend the funds.
|
||||
* @param _value The amount of tokens to be spent.
|
||||
*/
|
||||
function approve(address _spender, uint256 _value) public returns (bool) {
|
||||
allowed[msg.sender][_spender] = _value;
|
||||
Approval(msg.sender, _spender, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to check the amount of tokens that an owner allowed to a spender.
|
||||
* @param _owner address The address which owns the funds.
|
||||
* @param _spender address The address which will spend the funds.
|
||||
* @return A uint256 specifying the amount of tokens still available for the spender.
|
||||
*/
|
||||
function allowance(address _owner, address _spender) public view returns (uint256) {
|
||||
return allowed[_owner][_spender];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Increase the amount of tokens that an owner allowed to a spender.
|
||||
*
|
||||
* approve should be called when allowed[_spender] == 0. To increment
|
||||
* allowed value is better to use this function to avoid 2 calls (and wait until
|
||||
* the first transaction is mined)
|
||||
* From MonolithDAO Token.sol
|
||||
* @param _spender The address which will spend the funds.
|
||||
* @param _addedValue The amount of tokens to increase the allowance by.
|
||||
*/
|
||||
function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
|
||||
allowed[msg.sender][_spender] = allowed[msg.sender][_spender] + _addedValue;
|
||||
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Decrease the amount of tokens that an owner allowed to a spender.
|
||||
*
|
||||
* approve should be called when allowed[_spender] == 0. To decrement
|
||||
* allowed value is better to use this function to avoid 2 calls (and wait until
|
||||
* the first transaction is mined)
|
||||
* From MonolithDAO Token.sol
|
||||
* @param _spender The address which will spend the funds.
|
||||
* @param _subtractedValue The amount of tokens to decrease the allowance by.
|
||||
*/
|
||||
function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
|
||||
uint oldValue = allowed[msg.sender][_spender];
|
||||
if (_subtractedValue > oldValue) {
|
||||
allowed[msg.sender][_spender] = 0;
|
||||
} else {
|
||||
allowed[msg.sender][_spender] = oldValue - _subtractedValue;
|
||||
}
|
||||
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
pragma solidity ^0.4.11;
|
||||
|
||||
import "../LiquidPledging.sol";
|
||||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
34
index.js
34
index.js
|
@ -1,4 +1,30 @@
|
|||
exports.LiquidPledging = require('./lib/liquidPledging.js');
|
||||
exports.LiquidPledgingMock = require('./lib/liquidPledgingMock.js');
|
||||
exports.LiquidPledgingState = require('./lib/liquidPledgingState.js');
|
||||
exports.LPVault = require('./lib/vault.js');
|
||||
const contractInfo = require('./build/LPFactory.sol');
|
||||
const LiquidPledgingMockInfo = require('./build/LiquidPledgingMock.sol');
|
||||
const LPVaultInfo = require('./build/LPVault.sol');
|
||||
const StandardTokenInfo = require('./build/StandardToken.sol');
|
||||
const KernelInfo = require('./build/Kernel.sol');
|
||||
const ACLInfo = require('./build/ACL.sol');
|
||||
const generateClass = require('eth-contract-class').default;
|
||||
|
||||
module.exports = {
|
||||
LiquidPledging: generateClass(
|
||||
contractInfo.LiquidPledgingAbi,
|
||||
contractInfo.LiquidPledgingByteCode,
|
||||
),
|
||||
LPFactory: generateClass(contractInfo.LPFactoryAbi, contractInfo.LPFactoryByteCode),
|
||||
LiquidPledgingState: require('./lib/liquidPledgingState.js'),
|
||||
LPVault: generateClass(contractInfo.LPVaultAbi, contractInfo.LPVaultByteCode),
|
||||
Kernel: generateClass(KernelInfo.KernelAbi, KernelInfo.KernelByteCode),
|
||||
ACL: generateClass(ACLInfo.ACLAbi, ACLInfo.ACLByteCode),
|
||||
test: {
|
||||
StandardTokenTest: generateClass(
|
||||
StandardTokenInfo.StandardTokenAbi,
|
||||
StandardTokenInfo.StandardTokenByteCode,
|
||||
),
|
||||
assertFail: require('./test/helpers/assertFail'),
|
||||
LiquidPledgingMock: generateClass(
|
||||
LiquidPledgingMockInfo.LiquidPledgingMockAbi,
|
||||
LiquidPledgingMockInfo.LiquidPledgingMockByteCode,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
const LiquidPledgingAbi = require('../build/LiquidPledging.sol').LiquidPledgingAbi;
|
||||
const LiquidPledgingCode = require('../build/LiquidPledging.sol').LiquidPledgingByteCode;
|
||||
const generateClass = require('eth-contract-class').default;
|
||||
|
||||
module.exports = generateClass(LiquidPledgingAbi, LiquidPledgingCode);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
const LiquidPledgingMockAbi = require('../build/LiquidPledgingMock.sol').LiquidPledgingMockAbi;
|
||||
const LiquidPledgingMockCode = require('../build/LiquidPledgingMock.sol').LiquidPledgingMockByteCode;
|
||||
const generateClass = require('eth-contract-class').default;
|
||||
|
||||
module.exports = generateClass(LiquidPledgingMockAbi, LiquidPledgingMockCode);
|
|
@ -12,6 +12,7 @@ class LiquidPledgingState {
|
|||
.then((res) => {
|
||||
pledge.amount = res.amount;
|
||||
pledge.owner = res.owner;
|
||||
pledge.token = res.token;
|
||||
|
||||
if (res.intendedProject) {
|
||||
pledge.intendedProject = res.intendedProject;
|
||||
|
@ -68,12 +69,11 @@ class LiquidPledgingState {
|
|||
admin.name = res.name;
|
||||
admin.url = res.url;
|
||||
admin.commitTime = res.commitTime;
|
||||
if (admin.adminType === 'Project') {
|
||||
if (admin.type === 'Project') {
|
||||
admin.parentProject = res.parentProject;
|
||||
admin.canceled = res.canceled;
|
||||
}
|
||||
admin.plugin = res.plugin;
|
||||
admin.canceled = res.canceled;
|
||||
return admin;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
const LPVaultAbi = require('../build/LPVault.sol').LPVaultAbi;
|
||||
const LPVaultByteCode = require('../build/LPVault.sol').LPVaultByteCode;
|
||||
const generateClass = require('eth-contract-class').default;
|
||||
|
||||
module.exports = generateClass(LPVaultAbi, LPVaultByteCode);
|
|
@ -0,0 +1,135 @@
|
|||
'use strict';
|
||||
|
||||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
|
||||
|
||||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||
|
||||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
var LiquidPledgingState = function () {
|
||||
function LiquidPledgingState(liquidPledging) {
|
||||
_classCallCheck(this, LiquidPledgingState);
|
||||
|
||||
this.$lp = liquidPledging;
|
||||
}
|
||||
|
||||
_createClass(LiquidPledgingState, [{
|
||||
key: '$getPledge',
|
||||
value: function $getPledge(idPledge) {
|
||||
var _this = this;
|
||||
|
||||
var pledge = {
|
||||
delegates: []
|
||||
};
|
||||
|
||||
return this.$lp.getPledge(idPledge).then(function (res) {
|
||||
pledge.amount = res.amount;
|
||||
pledge.owner = res.owner;
|
||||
pledge.token = res.token;
|
||||
|
||||
if (res.intendedProject) {
|
||||
pledge.intendedProject = res.intendedProject;
|
||||
pledge.commmitTime = res.commitTime;
|
||||
}
|
||||
if (res.oldPledge) {
|
||||
pledge.oldPledge = res.oldPledge;
|
||||
}
|
||||
if (res.pledgeState === '0') {
|
||||
pledge.pledgeState = 'Pledged';
|
||||
} else if (res.pledgeState === '1') {
|
||||
pledge.pledgeState = 'Paying';
|
||||
} else if (res.pledgeState === '2') {
|
||||
pledge.pledgeState = 'Paid';
|
||||
} else {
|
||||
pledge.pledgeState = 'Unknown';
|
||||
}
|
||||
|
||||
var promises = [];
|
||||
for (var i = 1; i <= res.nDelegates; i += 1) {
|
||||
promises.push(_this.$lp.getPledgeDelegate(idPledge, i).then(function (r) {
|
||||
return {
|
||||
id: r.idDelegate,
|
||||
addr: r.addr,
|
||||
name: r.name,
|
||||
url: r.url
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(function (delegates) {
|
||||
pledge.delegates = delegates;
|
||||
return pledge;
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: '$getAdmin',
|
||||
value: function $getAdmin(idAdmin) {
|
||||
var admin = {};
|
||||
return this.$lp.getPledgeAdmin(idAdmin).then(function (res) {
|
||||
if (res.adminType === '0') {
|
||||
admin.type = 'Giver';
|
||||
} else if (res.adminType === '1') {
|
||||
admin.type = 'Delegate';
|
||||
} else if (res.adminType === '2') {
|
||||
admin.type = 'Project';
|
||||
} else {
|
||||
admin.type = 'Unknown';
|
||||
}
|
||||
admin.addr = res.addr;
|
||||
admin.name = res.name;
|
||||
admin.url = res.url;
|
||||
admin.commitTime = res.commitTime;
|
||||
if (admin.type === 'Project') {
|
||||
admin.parentProject = res.parentProject;
|
||||
admin.canceled = res.canceled;
|
||||
}
|
||||
admin.plugin = res.plugin;
|
||||
return admin;
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'getState',
|
||||
value: function getState() {
|
||||
var _this2 = this;
|
||||
|
||||
var getPledges = function getPledges() {
|
||||
return _this2.$lp.numberOfPledges().then(function (nPledges) {
|
||||
var promises = [];
|
||||
for (var i = 1; i <= nPledges; i += 1) {
|
||||
promises.push(_this2.$getPledge(i));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
});
|
||||
};
|
||||
|
||||
var getAdmins = function getAdmins() {
|
||||
return _this2.$lp.numberOfPledgeAdmins().then(function (nAdmins) {
|
||||
var promises = [];
|
||||
for (var i = 1; i <= nAdmins; i += 1) {
|
||||
promises.push(_this2.$getAdmin(i));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
};
|
||||
|
||||
return Promise.all([getPledges(), getAdmins()]).then(function (_ref) {
|
||||
var _ref2 = _slicedToArray(_ref, 2),
|
||||
pledges = _ref2[0],
|
||||
admins = _ref2[1];
|
||||
|
||||
return {
|
||||
pledges: [null].concat(_toConsumableArray(pledges)),
|
||||
admins: [null].concat(_toConsumableArray(admins))
|
||||
};
|
||||
});
|
||||
}
|
||||
}]);
|
||||
|
||||
return LiquidPledgingState;
|
||||
}();
|
||||
|
||||
module.exports = LiquidPledgingState;
|
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "liquidpledging",
|
||||
"version": "0.0.10",
|
||||
"name": "giveth-liquidpledging",
|
||||
"version": "0.1.1",
|
||||
"description": "Liquid Pledging Smart Contract",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
|
@ -9,7 +9,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "npm run build; mocha --harmony",
|
||||
"sol-compile": "solcpiler -i './contracts/**/*.sol'",
|
||||
"sol-compile":
|
||||
"solcpiler --solc-version v0.4.18+commit.9cf6e910 -i './contracts/**/*.sol' ./node_modules/@aragon/os/contracts/{kernel/Kernel.sol,acl/ACL.sol}",
|
||||
"js-compile": "babel -d lib/ js/",
|
||||
"build": "npm run sol-compile; npm run js-compile",
|
||||
"prepublish": "npm run build"
|
||||
|
@ -18,15 +19,7 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/Giveth/liquidpledging.git"
|
||||
},
|
||||
"keywords": [
|
||||
"liquid",
|
||||
"pledging",
|
||||
"tracking",
|
||||
"smart",
|
||||
"contract",
|
||||
"solidity",
|
||||
"donation"
|
||||
],
|
||||
"keywords": ["liquid", "pledging", "tracking", "smart", "contract", "solidity", "donation"],
|
||||
"author": "Jordi Baylina",
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
|
@ -40,18 +33,19 @@
|
|||
"eslint-plugin-import": "^2.6.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||
"eslint-plugin-react": "^7.1.0",
|
||||
"ethereumjs-testrpc": "git://github.com/perissology/testrpc.git#81216dbc",
|
||||
"ganache-cli": "^7.0.0-beta.0",
|
||||
"lerna": "^2.2.0",
|
||||
"random-bytes": "^1.0.0",
|
||||
"mocha": "^3.5.0",
|
||||
"solcpiler": "0.0.7",
|
||||
"web3": "1.0.0-beta.24"
|
||||
"random-bytes": "^1.0.0",
|
||||
"solcpiler": "https://github.com/perissology/solcpiler.git#6393e66",
|
||||
"web3": "1.0.0-beta.31"
|
||||
},
|
||||
"homepage": "https://github.com/Giveth/liquidpledging#readme",
|
||||
"dependencies": {
|
||||
"@aragon/os": "3.0.1",
|
||||
"async": "^2.4.0",
|
||||
"chai": "^4.1.0",
|
||||
"eth-contract-class": "0.0.6",
|
||||
"eth-contract-class": "^0.0.9",
|
||||
"giveth-common-contracts": "^0.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
/* eslint-env mocha */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const TestRPC = require('ethereumjs-testrpc');
|
||||
const TestRPC = require('ganache-cli');
|
||||
const Web3 = require('web3');
|
||||
const chai = require('chai');
|
||||
const liquidpledging = require('../index.js');
|
||||
const assertFail = require('./helpers/assertFail');
|
||||
const {
|
||||
LPVault,
|
||||
LPFactory,
|
||||
LiquidPledgingState,
|
||||
test,
|
||||
} = require('../index');
|
||||
|
||||
const LiquidPledging = liquidpledging.LiquidPledgingMock;
|
||||
const LiquidPledgingState = liquidpledging.LiquidPledgingState;
|
||||
const simpleProjectPluginFactoryAbi = require('../build/TestSimpleProjectPluginFactory.sol').TestSimpleProjectPluginFactoryAbi;
|
||||
const simpleProjectPluginFactoryByteCode = require('../build/TestSimpleProjectPluginFactory.sol').TestSimpleProjectPluginFactoryByteCode;
|
||||
const simpleProjectPluginRuntimeByteCode = require('../build/TestSimpleProjectPluginFactory.sol').TestSimpleProjectPluginRuntimeByteCode;
|
||||
const Vault = liquidpledging.LPVault;
|
||||
const simpleProjectPluginFactoryAbi = require('../build/TestSimpleProjectPluginFactory.sol')
|
||||
.TestSimpleProjectPluginFactoryAbi;
|
||||
const simpleProjectPluginFactoryByteCode = require('../build/TestSimpleProjectPluginFactory.sol')
|
||||
.TestSimpleProjectPluginFactoryByteCode;
|
||||
const simpleProjectPluginRuntimeByteCode = require('../build/TestSimpleProjectPluginFactory.sol')
|
||||
.TestSimpleProjectPluginRuntimeByteCode;
|
||||
const assert = chai.assert;
|
||||
|
||||
const printState = async (liquidPledgingState) => {
|
||||
const { StandardTokenTest, assertFail, LiquidPledgingMock } = test;
|
||||
|
||||
const printState = async liquidPledgingState => {
|
||||
const st = await liquidPledgingState.getState();
|
||||
console.log(JSON.stringify(st, null, 2));
|
||||
};
|
||||
|
||||
describe('LiquidPledging plugins test', function () {
|
||||
describe('LiquidPledging plugins test', function() {
|
||||
this.timeout(0);
|
||||
|
||||
let testrpc;
|
||||
|
@ -31,33 +37,46 @@ describe('LiquidPledging plugins test', function () {
|
|||
let giver1;
|
||||
let adminProject1;
|
||||
let adminDelegate1;
|
||||
let token;
|
||||
|
||||
before(async () => {
|
||||
testrpc = TestRPC.server({
|
||||
ws: true,
|
||||
gasLimit: 6500000,
|
||||
gasLimit: 6700000,
|
||||
total_accounts: 10,
|
||||
});
|
||||
|
||||
testrpc.listen(8546, '127.0.0.1');
|
||||
testrpc.listen(8545, '127.0.0.1');
|
||||
|
||||
web3 = new Web3('ws://localhost:8546');
|
||||
web3 = new Web3('http://localhost:8545');
|
||||
accounts = await web3.eth.getAccounts();
|
||||
giver1 = accounts[ 1 ];
|
||||
adminProject1 = accounts[ 2 ];
|
||||
adminDelegate1 = accounts[ 3 ];
|
||||
giver1 = accounts[1];
|
||||
adminProject1 = accounts[2];
|
||||
adminDelegate1 = accounts[3];
|
||||
});
|
||||
|
||||
after((done) => {
|
||||
after(done => {
|
||||
testrpc.close();
|
||||
done();
|
||||
});
|
||||
|
||||
it('Should deploy LiquidPledging contract', async function() {
|
||||
vault = await Vault.new(web3);
|
||||
liquidPledging = await LiquidPledging.new(web3, vault.$address, { gas: 6500000 });
|
||||
await vault.setLiquidPledging(liquidPledging.$address);
|
||||
const baseVault = await LPVault.new(web3, accounts[0]);
|
||||
const baseLP = await LiquidPledgingMock.new(web3, accounts[0]);
|
||||
lpFactory = await LPFactory.new(web3, baseVault.$address, baseLP.$address);
|
||||
|
||||
const r = await lpFactory.newLP(accounts[0], accounts[0]);
|
||||
|
||||
const vaultAddress = r.events.DeployVault.returnValues.vault;
|
||||
vault = new LPVault(web3, vaultAddress);
|
||||
|
||||
const lpAddress = r.events.DeployLiquidPledging.returnValues.liquidPledging;
|
||||
liquidPledging = new LiquidPledgingMock(web3, lpAddress);
|
||||
|
||||
liquidPledgingState = new LiquidPledgingState(liquidPledging);
|
||||
|
||||
token = await StandardTokenTest.new(web3);
|
||||
await token.mint(giver1, web3.utils.toWei('1000'));
|
||||
await token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver1 });
|
||||
});
|
||||
|
||||
it('Should create create giver with no plugin', async function() {
|
||||
|
@ -68,50 +87,57 @@ describe('LiquidPledging plugins test', function () {
|
|||
});
|
||||
|
||||
it('Should fail to create giver with invalid plugin', async function() {
|
||||
await assertFail(async () => {
|
||||
await liquidPledging.addGiver('Giver2', '', 0, vault.$address, { from: giver1 });
|
||||
});
|
||||
await assertFail(
|
||||
liquidPledging.addGiver('Giver2', '', 0, vault.$address, { from: giver1, gas: 4000000 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('Should fail to create delegate with invalid plugin', async function() {
|
||||
await assertFail(async () => {
|
||||
await liquidPledging.addDelegate('delegate1', '', 0, liquidPledging.$address, { from: adminDelegate1});
|
||||
});
|
||||
await assertFail(
|
||||
liquidPledging.addDelegate('delegate1', '', 0, liquidPledging.$address, {
|
||||
from: adminDelegate1,
|
||||
gas: 4000000,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Should fail to create project with invalid plugin', async function() {
|
||||
await assertFail(async () => {
|
||||
await liquidPledging.addProject('Project1', '', giver1, 0, 0, vault.$address, { from: adminProject1});
|
||||
});
|
||||
await assertFail(
|
||||
liquidPledging.addProject('Project1', '', giver1, 0, 0, vault.$address, {
|
||||
from: adminProject1,
|
||||
gas: 4000000,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Should deploy TestSimpleProjectPlugin and add project', async function() {
|
||||
// add plugin as valid plugin
|
||||
const codeHash = web3.utils.soliditySha3(simpleProjectPluginRuntimeByteCode);
|
||||
await liquidPledging.addValidPlugin(codeHash);
|
||||
await liquidPledging.addValidPluginContract(codeHash, { $extraGas: 200000 });
|
||||
|
||||
// deploy new plugin
|
||||
const factoryContract = await new web3.eth.Contract(simpleProjectPluginFactoryAbi)
|
||||
.deploy({
|
||||
data: simpleProjectPluginFactoryByteCode,
|
||||
arguments: []
|
||||
}).send({ from: adminProject1, gas: 5000000 });
|
||||
arguments: [],
|
||||
})
|
||||
.send({ from: adminProject1, gas: 5000000 });
|
||||
factoryContract.setProvider(web3.currentProvider);
|
||||
|
||||
await factoryContract.methods
|
||||
.deploy(liquidPledging.$address, "SimplePlugin1", "", 0)
|
||||
.send({ from: adminProject1, gas: 4000000 });
|
||||
.deploy(liquidPledging.$address, 'SimplePlugin1', '', 0)
|
||||
.send({ from: adminProject1, gas: 5000000 });
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, 2);
|
||||
});
|
||||
|
||||
it('Should allow all plugins', async function() {
|
||||
await liquidPledging.useWhitelist(false, { gas: 100000 });
|
||||
await liquidPledging.useWhitelist(false, { $extraGas: 200000 });
|
||||
|
||||
await liquidPledging.addGiver('Giver2', '', 0, vault.$address, { from: giver1, gas: 200000 });
|
||||
await liquidPledging.addGiver('Giver2', '', 0, vault.$address, { from: giver1 });
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, 3);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
/* eslint-env mocha */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const TestRPC = require('ethereumjs-testrpc');
|
||||
const TestRPC = require('ganache-cli');
|
||||
const Web3 = require('web3');
|
||||
const chai = require('chai');
|
||||
const liquidpledging = require('../index.js');
|
||||
const assertFail = require('./helpers/assertFail');
|
||||
const { assert } = require('chai');
|
||||
const { LPVault, LPFactory, LiquidPledgingState, test } = require('../index');
|
||||
|
||||
const LiquidPledging = liquidpledging.LiquidPledgingMock;
|
||||
const LiquidPledgingState = liquidpledging.LiquidPledgingState;
|
||||
const Vault = liquidpledging.LPVault;
|
||||
const assert = chai.assert;
|
||||
const { StandardTokenTest, assertFail, LiquidPledgingMock } = test;
|
||||
|
||||
const printState = async (liquidPledgingState) => {
|
||||
const printState = async liquidPledgingState => {
|
||||
const st = await liquidPledgingState.getState();
|
||||
console.log(JSON.stringify(st, null, 2));
|
||||
};
|
||||
|
||||
describe('LiquidPledging cancelPledge normal scenario', function () {
|
||||
describe('LiquidPledging cancelPledge normal scenario', function() {
|
||||
this.timeout(0);
|
||||
|
||||
let testrpc;
|
||||
|
@ -28,51 +24,64 @@ describe('LiquidPledging cancelPledge normal scenario', function () {
|
|||
let giver1;
|
||||
let adminProject1;
|
||||
let adminProject2;
|
||||
let token;
|
||||
|
||||
before(async () => {
|
||||
testrpc = TestRPC.server({
|
||||
ws: true,
|
||||
gasLimit: 5800000,
|
||||
gasLimit: 6700000,
|
||||
total_accounts: 10,
|
||||
});
|
||||
|
||||
testrpc.listen(8546, '127.0.0.1');
|
||||
testrpc.listen(8545, '127.0.0.1');
|
||||
|
||||
web3 = new Web3('ws://localhost:8546');
|
||||
web3 = new Web3('http://localhost:8545');
|
||||
accounts = await web3.eth.getAccounts();
|
||||
giver1 = accounts[ 1 ];
|
||||
adminProject1 = accounts[ 2 ];
|
||||
adminProject2 = accounts[ 3 ];
|
||||
giver1 = accounts[1];
|
||||
adminProject1 = accounts[2];
|
||||
adminProject2 = accounts[3];
|
||||
});
|
||||
|
||||
after((done) => {
|
||||
after(done => {
|
||||
testrpc.close();
|
||||
done();
|
||||
});
|
||||
|
||||
it('Should deploy LiquidPledging contract', async () => {
|
||||
vault = await Vault.new(web3);
|
||||
liquidPledging = await LiquidPledging.new(web3, vault.$address, { gas: 5800000 });
|
||||
await vault.setLiquidPledging(liquidPledging.$address);
|
||||
const baseVault = await LPVault.new(web3, accounts[0]);
|
||||
const baseLP = await LiquidPledgingMock.new(web3, accounts[0]);
|
||||
lpFactory = await LPFactory.new(web3, baseVault.$address, baseLP.$address);
|
||||
|
||||
const r = await lpFactory.newLP(accounts[0], accounts[0]);
|
||||
|
||||
const vaultAddress = r.events.DeployVault.returnValues.vault;
|
||||
vault = new LPVault(web3, vaultAddress);
|
||||
|
||||
const lpAddress = r.events.DeployLiquidPledging.returnValues.liquidPledging;
|
||||
liquidPledging = new LiquidPledgingMock(web3, lpAddress);
|
||||
|
||||
liquidPledgingState = new LiquidPledgingState(liquidPledging);
|
||||
|
||||
token = await StandardTokenTest.new(web3);
|
||||
await token.mint(giver1, web3.utils.toWei('1000'));
|
||||
await token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver1 });
|
||||
});
|
||||
|
||||
it('Should add project and donate ', async () => {
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 0, '0x0', { from: adminProject1 });
|
||||
await liquidPledging.donate(0, 1, { from: giver1, value: '1000', gas: 500000 });
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 0, '0x0', {
|
||||
from: adminProject1,
|
||||
});
|
||||
await liquidPledging.addGiverAndDonate(1, token.$address, 1000, { from: giver1 });
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, 2);
|
||||
});
|
||||
|
||||
it('Should only allow pledge owner to cancel pledge', async () => {
|
||||
await assertFail(async () => {
|
||||
await liquidPledging.cancelPledge(2, 1000, { from: giver1, gas: 500000 });
|
||||
});
|
||||
await assertFail(liquidPledging.cancelPledge(2, 1000, { from: giver1, gas: 4000000 }));
|
||||
});
|
||||
|
||||
it('Should cancel pledge and return to oldPledge', async () => {
|
||||
await liquidPledging.cancelPledge(2, 1000, { from: adminProject1, gas: 500000 });
|
||||
await liquidPledging.cancelPledge(2, 1000, { from: adminProject1, $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
|
||||
|
@ -81,9 +90,6 @@ describe('LiquidPledging cancelPledge normal scenario', function () {
|
|||
});
|
||||
|
||||
it('Should not allow to cancel pledge if oldPledge === 0', async () => {
|
||||
await assertFail(async () => {
|
||||
await liquidPledging.cancelPledge(1, 1000, { from: giver1, gas: 500000 });
|
||||
});
|
||||
})
|
||||
await assertFail(liquidPledging.cancelPledge(1, 1000, { from: giver1, gas: 4000000 }));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
/* eslint-env mocha */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const TestRPC = require('ethereumjs-testrpc');
|
||||
const TestRPC = require('ganache-cli');
|
||||
const Web3 = require('web3');
|
||||
const chai = require('chai');
|
||||
const liquidpledging = require('../index.js');
|
||||
const { assert } = require('chai');
|
||||
const { LPVault, LPFactory, LiquidPledgingState, test } = require('../index');
|
||||
|
||||
const LiquidPledging = liquidpledging.LiquidPledgingMock;
|
||||
const LiquidPledgingState = liquidpledging.LiquidPledgingState;
|
||||
const Vault = liquidpledging.LPVault;
|
||||
const assertFail = require('./helpers/assertFail');
|
||||
const assert = chai.assert;
|
||||
const { StandardTokenTest, assertFail, LiquidPledgingMock } = test;
|
||||
|
||||
const printState = async (liquidPledgingState) => {
|
||||
const printState = async liquidPledgingState => {
|
||||
const st = await liquidPledgingState.getState();
|
||||
console.log(JSON.stringify(st, null, 2));
|
||||
};
|
||||
|
||||
describe('DelegationChain test', function () {
|
||||
describe('DelegationChain test', function() {
|
||||
this.timeout(0);
|
||||
|
||||
let testrpc;
|
||||
|
@ -31,17 +27,18 @@ describe('DelegationChain test', function () {
|
|||
let delegate2;
|
||||
let delegate3;
|
||||
let adminProject1;
|
||||
let token;
|
||||
|
||||
const gasUsage = {};
|
||||
before(async () => {
|
||||
testrpc = TestRPC.server({
|
||||
ws: true,
|
||||
gasLimit: 5800000,
|
||||
gasLimit: 6700000,
|
||||
total_accounts: 10,
|
||||
});
|
||||
|
||||
testrpc.listen(8546, '127.0.0.1');
|
||||
testrpc.listen(8545, '127.0.0.1');
|
||||
|
||||
web3 = new Web3('ws://localhost:8546');
|
||||
web3 = new Web3('http://localhost:8545');
|
||||
accounts = await web3.eth.getAccounts();
|
||||
giver1 = accounts[1];
|
||||
delegate1 = accounts[2];
|
||||
|
@ -51,16 +48,29 @@ describe('DelegationChain test', function () {
|
|||
giver2 = accounts[6];
|
||||
});
|
||||
|
||||
after((done) => {
|
||||
after(done => {
|
||||
testrpc.close();
|
||||
done();
|
||||
});
|
||||
|
||||
it('Should deploy LiquidPledging contract', async () => {
|
||||
vault = await Vault.new(web3);
|
||||
liquidPledging = await LiquidPledging.new(web3, vault.$address, { gas: 5800000 });
|
||||
await vault.setLiquidPledging(liquidPledging.$address);
|
||||
const baseVault = await LPVault.new(web3, accounts[0]);
|
||||
const baseLP = await LiquidPledgingMock.new(web3, accounts[0]);
|
||||
lpFactory = await LPFactory.new(web3, baseVault.$address, baseLP.$address);
|
||||
|
||||
const r = await lpFactory.newLP(accounts[0], accounts[0]);
|
||||
|
||||
const vaultAddress = r.events.DeployVault.returnValues.vault;
|
||||
vault = new LPVault(web3, vaultAddress);
|
||||
|
||||
const lpAddress = r.events.DeployLiquidPledging.returnValues.liquidPledging;
|
||||
liquidPledging = new LiquidPledgingMock(web3, lpAddress);
|
||||
|
||||
liquidPledgingState = new LiquidPledgingState(liquidPledging);
|
||||
|
||||
token = await StandardTokenTest.new(web3);
|
||||
await token.mint(giver1, web3.utils.toWei('1000'));
|
||||
await token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver1 });
|
||||
});
|
||||
|
||||
it('Should add pledgeAdmins', async () => {
|
||||
|
@ -68,7 +78,9 @@ describe('DelegationChain test', function () {
|
|||
await liquidPledging.addDelegate('Delegate1', 'URLDelegate1', 259200, 0, { from: delegate1 }); // pledgeAdmin 2
|
||||
await liquidPledging.addDelegate('Delegate2', 'URLDelegate2', 0, 0, { from: delegate2 }); // pledgeAdmin 3
|
||||
await liquidPledging.addDelegate('Delegate3', 'URLDelegate3', 0, 0, { from: delegate3 }); // pledgeAdmin 4
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 0, 0, { from: adminProject1 }); // pledgeAdmin 5
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 0, 0, {
|
||||
from: adminProject1,
|
||||
}); // pledgeAdmin 5
|
||||
await liquidPledging.addGiver('Giver2', 'URLGiver2', 0, 0, { from: giver2 }); // pledgeAdmin 6
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
|
@ -76,11 +88,11 @@ describe('DelegationChain test', function () {
|
|||
});
|
||||
|
||||
it('Should allow previous delegate to transfer pledge', async () => {
|
||||
await liquidPledging.donate(1, 2, {from: giver1, value: 1000, $extraGas: 50000});
|
||||
await liquidPledging.donate(1, 2, token.$address, 1000, { from: giver1 });
|
||||
// add delegate2 to chain
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, { from: delegate1 });
|
||||
// delegate 1 transfer pledge back to self, thus undelegating delegate2
|
||||
await liquidPledging.transfer(2, 3, 1000, 2, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 3, 1000, 2, { from: delegate1, $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges[2].amount, 1000);
|
||||
|
@ -89,11 +101,11 @@ describe('DelegationChain test', function () {
|
|||
|
||||
it('Should allow any delegate in chain to transfer pledge and undelegate all delegates occurring later in chain', async () => {
|
||||
// add delegate2 to chain
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, { from: delegate1, $extraGas: 200000 });
|
||||
// add delegate3 to chain
|
||||
await liquidPledging.transfer(3, 3, 1000, 4, {from: delegate2, $extraGas: 100000});
|
||||
await liquidPledging.transfer(3, 3, 1000, 4, { from: delegate2, $extraGas: 200000 });
|
||||
// delegate 1 transfer pledge to project1. should also undelegate all delegates occurring later in chain
|
||||
await liquidPledging.transfer(2, 4, 1000, 5, {from: delegate1, $extraGas: 200000});
|
||||
await liquidPledging.transfer(2, 4, 1000, 5, { from: delegate1, $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges[5].amount, 1000);
|
||||
|
@ -105,15 +117,15 @@ describe('DelegationChain test', function () {
|
|||
});
|
||||
|
||||
it('Should throw if delegate2 is not in delegationChain', async () => {
|
||||
await assertFail(async () => await liquidPledging.transfer(3, 5, 1000, 1, {from: delegate2}));
|
||||
await assertFail(liquidPledging.transfer(3, 5, 1000, 1, { from: delegate2, gas: 4000000 }));
|
||||
});
|
||||
|
||||
it('Delegate1 should not be able to transfer to another giver', async () => {
|
||||
await assertFail(async () => await liquidPledging.transfer(2, 5, 1000, 6, {from: delegate1}));
|
||||
await assertFail(liquidPledging.transfer(2, 5, 1000, 6, { from: delegate1, gas: 4000000 }));
|
||||
});
|
||||
|
||||
it('Delegate1 should be able to transfer pledge back to owner', async () => {
|
||||
await liquidPledging.transfer(2, 5, 1000, 1, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 5, 1000, 1, { from: delegate1, $extraGas: 200000 });
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges[1].amount, 1000);
|
||||
assert.equal(st.pledges[1].delegates.length, 0);
|
||||
|
@ -122,11 +134,11 @@ describe('DelegationChain test', function () {
|
|||
|
||||
it('Delegate1 should be able to change delegation', async () => {
|
||||
// add delegate1 to chain
|
||||
await liquidPledging.transfer(1, 1, 1000, 2, {from: giver1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(1, 1, 1000, 2, { from: giver1, $extraGas: 200000 });
|
||||
// delegate1 add delegate2 to chain
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, { from: delegate1, $extraGas: 200000 });
|
||||
// delegate1 remove delegate2 and add delegate3 to chain
|
||||
await liquidPledging.transfer(2, 3, 1000, 4, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 3, 1000, 4, { from: delegate1, $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges[1].amount, 0);
|
||||
|
@ -138,9 +150,9 @@ describe('DelegationChain test', function () {
|
|||
|
||||
it('delegate in chain should be able to delegate to previous delegate, thus undelegating themselves and any delegate after the previous delegate', async () => {
|
||||
// add delegate2 to chain
|
||||
await liquidPledging.transfer(4, 6, 1000, 3, {from: delegate3, $extraGas: 100000});
|
||||
await liquidPledging.transfer(4, 6, 1000, 3, { from: delegate3 });
|
||||
// delegate2 transfer back to delegate1, thus undelegating delegate2 & delegate3
|
||||
await liquidPledging.transfer(3, 7, 1000, 2, {from: delegate2, $extraGas: 100000});
|
||||
await liquidPledging.transfer(3, 7, 1000, 2, { from: delegate2, $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges[7].amount, 0);
|
||||
|
@ -151,13 +163,13 @@ describe('DelegationChain test', function () {
|
|||
|
||||
it('Should not append delegate on veto delegation', async () => {
|
||||
// propose the delegation
|
||||
await liquidPledging.transfer(2, 2, 1000, 5, { from: delegate1, $extraGas: 100000 });
|
||||
await liquidPledging.transfer(2, 2, 1000, 5, { from: delegate1, $extraGas: 200000 });
|
||||
|
||||
const origPledge = await liquidPledging.getPledge(2);
|
||||
assert.equal(origPledge.amount, '0');
|
||||
|
||||
// veto the delegation
|
||||
await liquidPledging.transfer(1, 5, 1000, 2, { from: giver1, $extraGas: 100000 });
|
||||
await liquidPledging.transfer(1, 5, 1000, 2, { from: giver1, $extraGas: 200000 });
|
||||
|
||||
const currentPledge = await liquidPledging.getPledge(2);
|
||||
|
||||
|
@ -167,16 +179,54 @@ describe('DelegationChain test', function () {
|
|||
|
||||
it('Pledge should have longest commitTime in delegation chain', async () => {
|
||||
// delegate1 add delegate2 to chain
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 2, 1000, 3, { from: delegate1, $extraGas: 200000 });
|
||||
|
||||
// set the time
|
||||
const now = Math.floor(new Date().getTime() / 1000);
|
||||
await liquidPledging.setMockedTime(now);
|
||||
await liquidPledging.setMockedTime(now, { $extraGas: 200000 });
|
||||
|
||||
// propose project delegation
|
||||
await liquidPledging.transfer(3, 3, 1000, 5, { from: delegate2, $extraGas: 100000 });
|
||||
await liquidPledging.transfer(3, 3, 1000, 5, { from: delegate2 });
|
||||
|
||||
const pledge = await liquidPledging.getPledge(8);
|
||||
assert.equal(pledge.commitTime, now + 259200); // 259200 is longest commitTime in delegationChain
|
||||
})
|
||||
});
|
||||
|
||||
it("delegation chain should remain the same when owner veto's delegation", async () => {
|
||||
// owner veto delegation to project1
|
||||
await liquidPledging.transfer(1, 8, 1000, 3, { from: giver1, $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges[8].amount, 0);
|
||||
assert.equal(st.pledges[3].amount, 1000);
|
||||
assert.equal(st.pledges[3].delegates.length, 2);
|
||||
assert.equal(st.pledges[3].delegates[0].id, 2);
|
||||
assert.equal(st.pledges[3].delegates[1].id, 3);
|
||||
});
|
||||
|
||||
it("delegation chain should remain the same upto delegate of reciever when owner veto's delegation", async () => {
|
||||
// propose project1 delegation
|
||||
await liquidPledging.transfer(3, 3, 1000, 5, { from: delegate2, $extraGas: 200000 });
|
||||
// owner veto delegation to project1 and remove delegate2
|
||||
await liquidPledging.transfer(1, 8, 1000, 2, { from: giver1, $extraGas: 200000 });
|
||||
|
||||
const pledge = await liquidPledging.getPledge(2);
|
||||
assert.equal(pledge.amount, 1000);
|
||||
});
|
||||
|
||||
it('owner should be able to transfer pledge to a new delegate at any time', async () => {
|
||||
// propose project1 delegation
|
||||
await liquidPledging.transfer(2, 2, 1000, 5, { from: delegate1 });
|
||||
// owner veto delegation to project1 and assign new delgate
|
||||
await liquidPledging.transfer(1, 9, 1000, 3, { from: giver1, $extraGas: 200000 });
|
||||
|
||||
const pledge = await liquidPledging.getPledge(10);
|
||||
assert.equal(pledge.amount, 1000);
|
||||
assert.equal(pledge.nDelegates, 1);
|
||||
|
||||
// owner assign new delegate w/o vetoing intendedProject
|
||||
await liquidPledging.transfer(1, 10, 1000, 2, { from: giver1, $extraGas: 100000 });
|
||||
const pledge2 = await liquidPledging.getPledge(2);
|
||||
assert.equal(pledge2.amount, 1000);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,25 +1,19 @@
|
|||
/* eslint-env mocha */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const TestRPC = require('ethereumjs-testrpc');
|
||||
const TestRPC = require('ganache-cli');
|
||||
const Web3 = require('web3');
|
||||
const chai = require('chai');
|
||||
const liquidpledging = require('../index.js');
|
||||
const assertFail = require('./helpers/assertFail');
|
||||
const { assert } = require('chai');
|
||||
const { LPVault, LPFactory, LiquidPledgingState, Kernel, ACL, test } = require('../index');
|
||||
|
||||
const { StandardTokenTest, assertFail, LiquidPledgingMock } = test;
|
||||
const { utils } = Web3;
|
||||
|
||||
const LiquidPledging = liquidpledging.LiquidPledgingMock;
|
||||
const LPVault = liquidpledging.LPVault;
|
||||
const LiquidPledgingState = liquidpledging.LiquidPledgingState;
|
||||
const assert = chai.assert;
|
||||
|
||||
|
||||
const printState = async (liquidPledgingState) => {
|
||||
const printState = async liquidPledgingState => {
|
||||
const st = await liquidPledgingState.getState();
|
||||
console.log(JSON.stringify(st, null, 2));
|
||||
};
|
||||
|
||||
describe('LiquidPledging test', function () {
|
||||
describe('LiquidPledging test', function() {
|
||||
this.timeout(0);
|
||||
let testrpc;
|
||||
let web3;
|
||||
|
@ -35,16 +29,21 @@ describe('LiquidPledging test', function () {
|
|||
let adminProject2a;
|
||||
let adminProject3;
|
||||
let delegate2;
|
||||
let escapeHatchDestination;
|
||||
let escapeHatchCaller;
|
||||
let acl;
|
||||
let giver1Token;
|
||||
let giver2Token;
|
||||
|
||||
before(async () => {
|
||||
testrpc = TestRPC.server({
|
||||
ws: true,
|
||||
gasLimit: 5800000,
|
||||
total_accounts: 10,
|
||||
gasLimit: 6700000,
|
||||
total_accounts: 11,
|
||||
});
|
||||
|
||||
testrpc.listen(8546, '127.0.0.1');
|
||||
testrpc.listen(8545, '127.0.0.1');
|
||||
|
||||
web3 = new Web3('ws://localhost:8546');
|
||||
web3 = new Web3('http://localhost:8545');
|
||||
accounts = await web3.eth.getAccounts();
|
||||
giver1 = accounts[1];
|
||||
delegate1 = accounts[2];
|
||||
|
@ -54,21 +53,77 @@ describe('LiquidPledging test', function () {
|
|||
delegate2 = accounts[6];
|
||||
giver2 = accounts[7];
|
||||
adminProject3 = accounts[8];
|
||||
escapeHatchDestination = accounts[9];
|
||||
escapeHatchCaller = accounts[10];
|
||||
});
|
||||
|
||||
after((done) => {
|
||||
after(done => {
|
||||
testrpc.close();
|
||||
done();
|
||||
});
|
||||
|
||||
it('Should deploy LiquidPledging contract', async () => {
|
||||
vault = await LPVault.new(web3);
|
||||
liquidPledging = await LiquidPledging.new(web3, vault.$address, { gas: 5800000 });
|
||||
await vault.setLiquidPledging(liquidPledging.$address);
|
||||
const baseVault = await LPVault.new(web3, escapeHatchDestination);
|
||||
const baseLP = await LiquidPledgingMock.new(web3, escapeHatchDestination, {
|
||||
gas: 6700000,
|
||||
});
|
||||
lpFactory = await LPFactory.new(web3, baseVault.$address, baseLP.$address);
|
||||
|
||||
assert.isAbove(Number(await baseVault.getInitializationBlock()), 0);
|
||||
assert.isAbove(Number(await baseLP.getInitializationBlock()), 0);
|
||||
|
||||
const r = await lpFactory.newLP(accounts[0], escapeHatchDestination);
|
||||
|
||||
const vaultAddress = r.events.DeployVault.returnValues.vault;
|
||||
vault = new LPVault(web3, vaultAddress);
|
||||
|
||||
const lpAddress = r.events.DeployLiquidPledging.returnValues.liquidPledging;
|
||||
liquidPledging = new LiquidPledgingMock(web3, lpAddress);
|
||||
|
||||
liquidPledgingState = new LiquidPledgingState(liquidPledging);
|
||||
|
||||
// set permissions
|
||||
const kernel = new Kernel(web3, await liquidPledging.kernel());
|
||||
acl = new ACL(web3, await kernel.acl());
|
||||
await acl.createPermission(
|
||||
accounts[0],
|
||||
vault.$address,
|
||||
await vault.CANCEL_PAYMENT_ROLE(),
|
||||
accounts[0],
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
await acl.createPermission(
|
||||
accounts[0],
|
||||
vault.$address,
|
||||
await vault.CONFIRM_PAYMENT_ROLE(),
|
||||
accounts[0],
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
await acl.grantPermission(
|
||||
escapeHatchCaller,
|
||||
vault.$address,
|
||||
await vault.ESCAPE_HATCH_CALLER_ROLE(),
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
await acl.revokePermission(
|
||||
accounts[0],
|
||||
vault.$address,
|
||||
await vault.ESCAPE_HATCH_CALLER_ROLE(),
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
|
||||
giver1Token = await StandardTokenTest.new(web3);
|
||||
giver2Token = await StandardTokenTest.new(web3);
|
||||
|
||||
await giver1Token.mint(giver1, web3.utils.toWei('1000'));
|
||||
await giver2Token.mint(giver2, web3.utils.toWei('1000'));
|
||||
|
||||
await giver1Token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver1 });
|
||||
await giver2Token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver2 });
|
||||
});
|
||||
it('Should create a giver', async () => {
|
||||
await liquidPledging.addGiver('Giver1', 'URLGiver1', 86400, 0, { from: giver1 });
|
||||
await liquidPledging.addGiver('Giver1', 'URLGiver1', 86400, 0, { from: giver1, gas: 1000000 });
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, 1);
|
||||
const res = await liquidPledging.getPledgeAdmin(1);
|
||||
|
@ -79,10 +134,19 @@ describe('LiquidPledging test', function () {
|
|||
assert.equal(res[4], 86400);
|
||||
});
|
||||
it('Should make a donation', async () => {
|
||||
await liquidPledging.donate(1, 1, { from: giver1, value: utils.toWei('1') });
|
||||
const r = await liquidPledging.donate(1, 1, giver1Token.$address, utils.toWei('1'), {
|
||||
from: giver1,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(nPledges, 1);
|
||||
await liquidPledging.getPledge(1);
|
||||
const p = await liquidPledging.getPledge(1);
|
||||
assert.equal(p.amount, utils.toWei('1'));
|
||||
assert.equal(p.owner, 1);
|
||||
const vaultBal = await giver1Token.balanceOf(vault.$address);
|
||||
const giver1Bal = await giver1Token.balanceOf(giver1);
|
||||
assert.equal(vaultBal, web3.utils.toWei('1'));
|
||||
assert.equal(giver1Bal, web3.utils.toWei('999'));
|
||||
});
|
||||
it('Should create a delegate', async () => {
|
||||
await liquidPledging.addDelegate('Delegate1', 'URLDelegate1', 0, 0, { from: delegate1 });
|
||||
|
@ -96,7 +160,7 @@ describe('LiquidPledging test', function () {
|
|||
assert.equal(res[4], 0);
|
||||
});
|
||||
it('Giver should delegate on the delegate', async () => {
|
||||
await liquidPledging.transfer(1, 1, utils.toWei('0.5'), 2, { from: giver1 });
|
||||
await liquidPledging.transfer(1, 1, utils.toWei('0.5'), 2, { from: giver1, $extraGas: 100000 });
|
||||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(nPledges, 2);
|
||||
const res1 = await liquidPledging.getPledge(1);
|
||||
|
@ -111,7 +175,9 @@ describe('LiquidPledging test', function () {
|
|||
assert.equal(d[2], 'Delegate1');
|
||||
});
|
||||
it('Should create a 2 projects', async () => {
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 86400, 0, { from: adminProject1 });
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 86400, 0, {
|
||||
from: adminProject1,
|
||||
});
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, 3);
|
||||
|
@ -124,7 +190,9 @@ describe('LiquidPledging test', function () {
|
|||
assert.equal(res[5], 0);
|
||||
assert.equal(res[6], false);
|
||||
|
||||
await liquidPledging.addProject('Project2', 'URLProject2', adminProject2, 0, 86400, 0, { from: adminProject2 });
|
||||
await liquidPledging.addProject('Project2', 'URLProject2', adminProject2, 0, 86400, 0, {
|
||||
from: adminProject2,
|
||||
});
|
||||
|
||||
const nAdmins2 = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins2, 4);
|
||||
|
@ -143,56 +211,60 @@ describe('LiquidPledging test', function () {
|
|||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(nPledges, 3);
|
||||
const res3 = await liquidPledging.getPledge(3);
|
||||
assert.equal(res3[0], utils.toWei('0.2'));
|
||||
assert.equal(res3[1], 1); // Owner
|
||||
assert.equal(res3[2], 1); // Delegates
|
||||
assert.equal(res3[3], 3); // Proposed Project
|
||||
assert.isAbove(utils.toDecimal(res3[4]), n + 86000);
|
||||
assert.equal(res3[5], 0); // Old Node
|
||||
assert.equal(res3[6], 0); // Not Paid
|
||||
assert.equal(res3.amount, utils.toWei('0.2'));
|
||||
assert.equal(res3.owner, 1);
|
||||
assert.equal(res3.nDelegates, 1);
|
||||
assert.equal(res3.intendedProject, 3);
|
||||
assert.isAbove(utils.toDecimal(res3.commitTime), n + 86000);
|
||||
assert.equal(res3.oldPledge, 0);
|
||||
assert.equal(res3.token, giver1Token.$address);
|
||||
assert.equal(res3.pledgeState, 0); // Not Paid
|
||||
});
|
||||
it('Giver should change his mind and assign half of it to project2', async () => {
|
||||
await liquidPledging.transfer(1, 3, utils.toWei('0.1'), 4, { from: giver1 });
|
||||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(nPledges, 4);
|
||||
const res3 = await liquidPledging.getPledge(3);
|
||||
assert.equal(res3[0], utils.toWei('0.1'));
|
||||
assert.equal(res3.amount, utils.toWei('0.1'));
|
||||
const res4 = await liquidPledging.getPledge(4);
|
||||
assert.equal(res4[1], 4); // Owner
|
||||
assert.equal(res4[2], 0); // Delegates
|
||||
assert.equal(res4[3], 0); // Proposed Project
|
||||
assert.equal(res4[4], 0);
|
||||
assert.equal(res4[5], 2); // Old Node
|
||||
assert.equal(res4[6], 0); // Not Paid
|
||||
assert.equal(res4.owner, 4);
|
||||
assert.equal(res4.nDelegates, 0);
|
||||
assert.equal(res4.intendedProject, 0);
|
||||
assert.equal(res4.commitTime, 0);
|
||||
assert.equal(res4.oldPledge, 2);
|
||||
assert.equal(res4.token, giver1Token.$address);
|
||||
assert.equal(res4.pledgeState, 0); // Not Paid
|
||||
});
|
||||
it('After the time, the project1 should be able to spend part of it', async () => {
|
||||
const n = Math.floor(new Date().getTime() / 1000);
|
||||
await liquidPledging.setMockedTime(n + 86401);
|
||||
await liquidPledging.setMockedTime(n + 86401, { $extraGas: 100000 });
|
||||
await liquidPledging.withdraw(3, utils.toWei('0.05'), { from: adminProject1 });
|
||||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(nPledges, 6);
|
||||
const res5 = await liquidPledging.getPledge(5);
|
||||
assert.equal(res5[0], utils.toWei('0.05'));
|
||||
assert.equal(res5[1], 3); // Owner
|
||||
assert.equal(res5[2], 0); // Delegates
|
||||
assert.equal(res5[3], 0); // Proposed Project
|
||||
assert.equal(res5[4], 0); // commit time
|
||||
assert.equal(res5[5], 2); // Old Node
|
||||
assert.equal(res5[6], 0); // Not Paid
|
||||
assert.equal(res5.amount, utils.toWei('0.05'));
|
||||
assert.equal(res5.owner, 3);
|
||||
assert.equal(res5.nDelegates, 0);
|
||||
assert.equal(res5.intendedProject, 0);
|
||||
assert.equal(res5.commitTime, 0);
|
||||
assert.equal(res5.oldPledge, 2);
|
||||
assert.equal(res5.token, giver1Token.$address);
|
||||
assert.equal(res5.pledgeState, 0); // Not Paid
|
||||
const res6 = await liquidPledging.getPledge(6);
|
||||
assert.equal(res6[0], utils.toWei('0.05'));
|
||||
assert.equal(res6[1], 3); // Owner
|
||||
assert.equal(res6[2], 0); // Delegates
|
||||
assert.equal(res6[3], 0); // Proposed Project
|
||||
assert.equal(res6[4], 0); // commit time
|
||||
assert.equal(res6[5], 2); // Old Node
|
||||
assert.equal(res6[6], 1); // Peinding paid Paid
|
||||
assert.equal(res6.amount, utils.toWei('0.05'));
|
||||
assert.equal(res6.owner, 3);
|
||||
assert.equal(res6.nDelegates, 0);
|
||||
assert.equal(res6.intendedProject, 0);
|
||||
assert.equal(res6.commitTime, 0);
|
||||
assert.equal(res6.oldPledge, 2);
|
||||
assert.equal(res6.token, giver1Token.$address);
|
||||
assert.equal(res6.pledgeState, 1); // Pending
|
||||
});
|
||||
it('Should collect the Ether', async () => {
|
||||
const initialBalance = await web3.eth.getBalance(adminProject1);
|
||||
it('Should collect the token', async () => {
|
||||
const initialBalance = await giver1Token.balanceOf(adminProject1);
|
||||
|
||||
await vault.confirmPayment(0);
|
||||
const finalBalance = await web3.eth.getBalance(adminProject1);
|
||||
await vault.confirmPayment(0, { $extraGas: 200000 });
|
||||
const finalBalance = await giver1Token.balanceOf(adminProject1);
|
||||
|
||||
const collected = utils.fromWei(utils.toBN(finalBalance).sub(utils.toBN(initialBalance)));
|
||||
|
||||
|
@ -201,30 +273,32 @@ describe('LiquidPledging test', function () {
|
|||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(nPledges, 7);
|
||||
const res7 = await liquidPledging.getPledge(7);
|
||||
assert.equal(res7[0], utils.toWei('0.05'));
|
||||
assert.equal(res7[1], 3); // Owner
|
||||
assert.equal(res7[2], 0); // Delegates
|
||||
assert.equal(res7[3], 0); // Proposed Project
|
||||
assert.equal(res7[4], 0); // commit time
|
||||
assert.equal(res7[5], 2); // Old Node
|
||||
assert.equal(res7[6], 2); // Peinding paid Paid
|
||||
assert.equal(res7.amount, utils.toWei('0.05'));
|
||||
assert.equal(res7.owner, 3);
|
||||
assert.equal(res7.nDelegates, 0);
|
||||
assert.equal(res7.intendedProject, 0);
|
||||
assert.equal(res7.commitTime, 0);
|
||||
assert.equal(res7.oldPledge, 2);
|
||||
assert.equal(res7.token, giver1Token.$address);
|
||||
assert.equal(res7.pledgeState, 2); // Pending
|
||||
});
|
||||
it('Admin of the project1 should be able to cancel project1', async () => {
|
||||
await liquidPledging.cancelProject(3, { from: adminProject1 });
|
||||
await liquidPledging.cancelProject(3, { from: adminProject1, $extraGas: 100000 });
|
||||
const st = await liquidPledgingState.getState(liquidPledging);
|
||||
assert.equal(st.admins[3].canceled, true);
|
||||
});
|
||||
it('Should not allow to withdraw from a canceled project', async () => {
|
||||
const st = await liquidPledgingState.getState(liquidPledging);
|
||||
assert.equal(utils.fromWei(st.pledges[5].amount), 0.05);
|
||||
await assertFail(async () => {
|
||||
await liquidPledging.withdraw(5, utils.toWei('0.01'), { from: adminProject1 });
|
||||
});
|
||||
const p = await liquidPledging.getPledge(5);
|
||||
assert.equal(utils.fromWei(p.amount), 0.05);
|
||||
|
||||
await assertFail(
|
||||
liquidPledging.withdraw(5, utils.toWei('0.01'), { from: adminProject1, gas: 4000000 }),
|
||||
);
|
||||
});
|
||||
it('Delegate should send part of this ETH to project2', async () => {
|
||||
await liquidPledging.transfer(2, 5, utils.toWei('0.03'), 4, {
|
||||
$extraGas: 100000,
|
||||
from: delegate1,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
const st = await liquidPledgingState.getState(liquidPledging);
|
||||
assert.equal(st.pledges.length, 9);
|
||||
|
@ -235,27 +309,38 @@ describe('LiquidPledging test', function () {
|
|||
assert.equal(st.pledges[8].intendedProject, 4);
|
||||
});
|
||||
it('Giver should be able to send the remaining to project2', async () => {
|
||||
await liquidPledging.transfer(1, 5, utils.toWei('0.02'), 4, { from: giver1, $extraGas: 100000 });
|
||||
await liquidPledging.transfer(1, 5, utils.toWei('0.02'), 4, {
|
||||
from: giver1,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
const st = await liquidPledgingState.getState(liquidPledging);
|
||||
assert.equal(st.pledges.length, 9);
|
||||
assert.equal(utils.fromWei(st.pledges[5].amount), 0);
|
||||
assert.equal(utils.fromWei(st.pledges[4].amount), 0.12);
|
||||
});
|
||||
it('A subproject 2a and a delegate2 is created', async () => {
|
||||
await liquidPledging.addProject('Project2a', 'URLProject2a', adminProject2a, 4, 86400, 0, { from: adminProject2 });
|
||||
await liquidPledging.addProject('Project2a', 'URLProject2a', adminProject2a, 4, 86400, 0, {
|
||||
from: adminProject2,
|
||||
});
|
||||
await liquidPledging.addDelegate('Delegate2', 'URLDelegate2', 0, 0, { from: delegate2 });
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, 6);
|
||||
});
|
||||
it('Project 2 delegate in delegate2', async () => {
|
||||
await liquidPledging.transfer(4, 4, utils.toWei('0.02'), 6, { from: adminProject2 });
|
||||
const st = await liquidPledgingState.getState(liquidPledging);
|
||||
await liquidPledging.transfer(4, 4, utils.toWei('0.02'), 6, {
|
||||
from: adminProject2,
|
||||
$extraGas: 200000,
|
||||
});
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges.length, 10);
|
||||
assert.equal(utils.fromWei(st.pledges[9].amount), 0.02);
|
||||
assert.equal(utils.fromWei(st.pledges[4].amount), 0.1);
|
||||
});
|
||||
it('delegate2 assigns to projec2a', async () => {
|
||||
await liquidPledging.transfer(6, 9, utils.toWei('0.01'), 5, { from: delegate2 });
|
||||
await liquidPledging.transfer(6, 9, utils.toWei('0.01'), 5, {
|
||||
from: delegate2,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
const st = await liquidPledgingState.getState(liquidPledging);
|
||||
assert.equal(st.pledges.length, 11);
|
||||
assert.equal(utils.fromWei(st.pledges[9].amount), 0.01);
|
||||
|
@ -263,8 +348,11 @@ describe('LiquidPledging test', function () {
|
|||
});
|
||||
it('project2a authorize to spend a litle', async () => {
|
||||
const n = Math.floor(new Date().getTime() / 1000);
|
||||
await liquidPledging.setMockedTime(n + (86401 * 3));
|
||||
await liquidPledging.withdraw(10, utils.toWei('0.005'), { from: adminProject2a });
|
||||
await liquidPledging.setMockedTime(n + 86401 * 3, { $extraGas: 200000 });
|
||||
await liquidPledging.withdraw(10, utils.toWei('0.005'), {
|
||||
from: adminProject2a,
|
||||
$extraGas: 200000,
|
||||
});
|
||||
const st = await liquidPledgingState.getState(liquidPledging);
|
||||
assert.equal(st.pledges.length, 13);
|
||||
assert.equal(utils.fromWei(st.pledges[10].amount), 0);
|
||||
|
@ -272,12 +360,12 @@ describe('LiquidPledging test', function () {
|
|||
assert.equal(utils.fromWei(st.pledges[12].amount), 0.005);
|
||||
});
|
||||
it('project2 is canceled', async () => {
|
||||
await liquidPledging.cancelProject(4, { from: adminProject2 });
|
||||
await liquidPledging.cancelProject(4, { from: adminProject2, $extraGas: 100000 });
|
||||
});
|
||||
it('Should not be able to withdraw it', async () => {
|
||||
await assertFail(async () => {
|
||||
await liquidPledging.withdraw(12, utils.toWei('0.005'), { from: giver1 });
|
||||
});
|
||||
await assertFail(
|
||||
liquidPledging.withdraw(12, utils.toWei('0.005'), { from: giver1, gas: 4000000 }),
|
||||
);
|
||||
});
|
||||
it('Should be able to cancel payment', async () => {
|
||||
// bug somewhere which will throw invalid op_code if we don't provide gas or extraGas
|
||||
|
@ -299,15 +387,19 @@ describe('LiquidPledging test', function () {
|
|||
|
||||
// .substring is to remove the 0x prefix on the toHex result
|
||||
const encodedPledges = pledges.map(p => {
|
||||
return '0x' + utils.padLeft(utils.toHex(p.amount).substring(2), 48) + utils.padLeft(utils.toHex(p.id).substring(2), 16);
|
||||
return (
|
||||
'0x' +
|
||||
utils.padLeft(utils.toHex(p.amount).substring(2), 48) +
|
||||
utils.padLeft(utils.toHex(p.id).substring(2), 16)
|
||||
);
|
||||
});
|
||||
|
||||
await liquidPledging.mWithdraw(encodedPledges, { from: giver1, $extraGas: 500000 });
|
||||
await liquidPledging.mWithdraw(encodedPledges, { from: giver1, $extraGas: 200000 });
|
||||
|
||||
const initialBalance = await web3.eth.getBalance(giver1);
|
||||
await vault.multiConfirm([2, 3, 4, 5, 6]);
|
||||
const initialBalance = await giver1Token.balanceOf(giver1);
|
||||
await vault.multiConfirm([2, 3, 4, 5, 6], { $extraGas: 200000 });
|
||||
|
||||
const finalBalance = await web3.eth.getBalance(giver1);
|
||||
const finalBalance = await giver1Token.balanceOf(giver1);
|
||||
const collected = utils.fromWei(utils.toBN(finalBalance).sub(utils.toBN(initialBalance)));
|
||||
|
||||
assert.equal(collected, 0.95);
|
||||
|
@ -315,9 +407,12 @@ describe('LiquidPledging test', function () {
|
|||
it('Should make a donation and create giver', async () => {
|
||||
const oldNPledges = await liquidPledging.numberOfPledges();
|
||||
const oldNAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
await liquidPledging.donate(0, 1, { from: giver2, value: utils.toWei('1') });
|
||||
await liquidPledging.addGiverAndDonate(1, giver2Token.$address, utils.toWei('1'), {
|
||||
from: giver2,
|
||||
$extraGas: 200000,
|
||||
});
|
||||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(utils.toDecimal(nPledges), utils.toDecimal(oldNPledges) + 1);
|
||||
assert.equal(utils.toDecimal(nPledges), utils.toDecimal(oldNPledges) + 2);
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(utils.toDecimal(nAdmins), utils.toDecimal(oldNAdmins) + 1);
|
||||
const res = await liquidPledging.getPledgeAdmin(nAdmins);
|
||||
|
@ -326,11 +421,77 @@ describe('LiquidPledging test', function () {
|
|||
assert.equal(res[2], '');
|
||||
assert.equal(res[3], '');
|
||||
assert.equal(res[4], 259200); // default to 3 day commitTime
|
||||
const giver2Bal = await giver2Token.balanceOf(giver2);
|
||||
assert.equal(giver2Bal, utils.toWei('999'));
|
||||
});
|
||||
it('Should allow childProject with different parentProject owner', async () => {
|
||||
const nAdminsBefore = await liquidPledging.numberOfPledgeAdmins();
|
||||
await liquidPledging.addProject('Project3', 'URLProject3', adminProject3, 4, 86400, 0, { from: adminProject3 });
|
||||
await liquidPledging.addProject('Project3', 'URLProject3', adminProject3, 4, 86400, 0, {
|
||||
from: adminProject3,
|
||||
});
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, utils.toDecimal(nAdminsBefore) + 1);
|
||||
});
|
||||
|
||||
it('should throw if projectLevel > 20', async () => {
|
||||
let nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
|
||||
await liquidPledging.addProject('ProjectLevel0', '', adminProject1, 0, 86400, 0, {
|
||||
from: adminProject1,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
|
||||
for (let i = 2; i <= 20; i++) {
|
||||
await liquidPledging.addProject(`ProjectLevel${i}`, '', adminProject1, ++nAdmins, 86400, 0, {
|
||||
from: adminProject1,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
}
|
||||
|
||||
await assertFail(
|
||||
liquidPledging.addProject('ProjectLevel21', '', adminProject1, ++nAdmins, 86400, 0, {
|
||||
from: adminProject1,
|
||||
gas: 4000000,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should prevent donation to 0 receiverId', async () => {
|
||||
await assertFail(
|
||||
liquidPledging.donate(1, 0, giver1Token.$address, 1, { from: giver1, gas: 6700000 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should prevent donation from 0 giverId', async () => {
|
||||
await assertFail(
|
||||
liquidPledging.donate(0, 1, giver1Token.$address, 1, { from: giver1, gas: 6700000 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should donate on behalf of another addy', async () => {
|
||||
const oldNPledges = await liquidPledging.numberOfPledges();
|
||||
const oldNAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
const preGiver1Bal = await giver1Token.balanceOf(giver1);
|
||||
|
||||
await liquidPledging.addGiverAndDonate(1, accounts[8], giver1Token.$address, 11, {
|
||||
from: giver1,
|
||||
$extraGas: 200000,
|
||||
});
|
||||
|
||||
const nPledges = await liquidPledging.numberOfPledges();
|
||||
assert.equal(utils.toDecimal(nPledges), utils.toDecimal(oldNPledges) + 1);
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(utils.toDecimal(nAdmins), utils.toDecimal(oldNAdmins) + 1);
|
||||
|
||||
const res = await liquidPledging.getPledgeAdmin(nAdmins);
|
||||
assert.equal(res[0], 0); // Giver
|
||||
assert.equal(res[1], accounts[8]);
|
||||
assert.equal(res[2], '');
|
||||
assert.equal(res[3], '');
|
||||
assert.equal(res[4], 259200); // default to 3 day commitTime
|
||||
|
||||
const giver1Bal = await giver1Token.balanceOf(giver1);
|
||||
assert.equal(new utils.BN(preGiver1Bal).subn(11).toString(), giver1Bal);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
/* eslint-env mocha */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const TestRPC = require('ethereumjs-testrpc');
|
||||
const TestRPC = require('ganache-cli');
|
||||
const Web3 = require('web3');
|
||||
const chai = require('chai');
|
||||
const liquidpledging = require('../index.js');
|
||||
const { assert } = require('chai');
|
||||
const { LPVault, LPFactory, LiquidPledgingState, test } = require('../index');
|
||||
|
||||
const LiquidPledging = liquidpledging.LiquidPledgingMock;
|
||||
const LiquidPledgingState = liquidpledging.LiquidPledgingState;
|
||||
const Vault = liquidpledging.LPVault;
|
||||
const assertFail = require('./helpers/assertFail');
|
||||
const assert = chai.assert;
|
||||
const { StandardTokenTest, assertFail, LiquidPledgingMock } = test;
|
||||
|
||||
const printState = async (liquidPledgingState) => {
|
||||
const printState = async liquidPledgingState => {
|
||||
const st = await liquidPledgingState.getState();
|
||||
console.log(JSON.stringify(st, null, 2));
|
||||
};
|
||||
|
||||
describe('NormalizePledge test', function () {
|
||||
describe('NormalizePledge test', function() {
|
||||
this.timeout(0);
|
||||
|
||||
let testrpc;
|
||||
|
@ -31,17 +27,17 @@ describe('NormalizePledge test', function () {
|
|||
let delegate2;
|
||||
let adminProject1;
|
||||
let adminProject2;
|
||||
let token;
|
||||
|
||||
before(async () => {
|
||||
testrpc = TestRPC.server({
|
||||
ws: true,
|
||||
gasLimit: 5800000,
|
||||
gasLimit: 6700000,
|
||||
total_accounts: 10,
|
||||
});
|
||||
|
||||
testrpc.listen(8546, '127.0.0.1');
|
||||
testrpc.listen(8545, '127.0.0.1');
|
||||
|
||||
web3 = new Web3('ws://localhost:8546');
|
||||
web3 = new Web3('http://localhost:8545');
|
||||
accounts = await web3.eth.getAccounts();
|
||||
giver1 = accounts[1];
|
||||
delegate1 = accounts[2];
|
||||
|
@ -51,24 +47,43 @@ describe('NormalizePledge test', function () {
|
|||
giver2 = accounts[6];
|
||||
});
|
||||
|
||||
after((done) => {
|
||||
after(done => {
|
||||
testrpc.close();
|
||||
done();
|
||||
});
|
||||
|
||||
it('Should deploy LiquidPledging contract', async () => {
|
||||
vault = await Vault.new(web3);
|
||||
liquidPledging = await LiquidPledging.new(web3, vault.$address, { gas: 5800000 });
|
||||
await vault.setLiquidPledging(liquidPledging.$address);
|
||||
const baseVault = await LPVault.new(web3, accounts[0]);
|
||||
const baseLP = await LiquidPledgingMock.new(web3, accounts[0]);
|
||||
lpFactory = await LPFactory.new(web3, baseVault.$address, baseLP.$address);
|
||||
|
||||
const r = await lpFactory.newLP(accounts[0], accounts[0]);
|
||||
|
||||
const vaultAddress = r.events.DeployVault.returnValues.vault;
|
||||
vault = new LPVault(web3, vaultAddress);
|
||||
|
||||
const lpAddress = r.events.DeployLiquidPledging.returnValues.liquidPledging;
|
||||
liquidPledging = new LiquidPledgingMock(web3, lpAddress);
|
||||
|
||||
liquidPledgingState = new LiquidPledgingState(liquidPledging);
|
||||
|
||||
token = await StandardTokenTest.new(web3);
|
||||
await token.mint(giver1, web3.utils.toWei('1000'));
|
||||
await token.mint(giver2, web3.utils.toWei('1000'));
|
||||
await token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver1 });
|
||||
await token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver2 });
|
||||
});
|
||||
|
||||
it('Should add pledgeAdmins', async () => {
|
||||
await liquidPledging.addGiver('Giver1', 'URLGiver1', 86400, 0, { from: giver1 }); // pledgeAdmin 1
|
||||
await liquidPledging.addDelegate('Delegate1', 'URLDelegate1', 259200, 0, { from: delegate1 }); // pledgeAdmin 2
|
||||
await liquidPledging.addDelegate('Delegate2', 'URLDelegate2', 0, 0, { from: delegate2 }); // pledgeAdmin 3
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 0, 0, { from: adminProject1 }); // pledgeAdmin 4
|
||||
await liquidPledging.addProject('Project2', 'URLProject2', adminProject2, 0, 0, 0, { from: adminProject2 }); // pledgeAdmin 5
|
||||
await liquidPledging.addProject('Project1', 'URLProject1', adminProject1, 0, 0, 0, {
|
||||
from: adminProject1,
|
||||
}); // pledgeAdmin 4
|
||||
await liquidPledging.addProject('Project2', 'URLProject2', adminProject2, 0, 0, 0, {
|
||||
from: adminProject2,
|
||||
}); // pledgeAdmin 5
|
||||
await liquidPledging.addGiver('Giver2', 'URLGiver2', 0, 0, { from: giver2 }); // pledgeAdmin 6
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
|
@ -77,25 +92,25 @@ describe('NormalizePledge test', function () {
|
|||
|
||||
it('Should commit pledges if commitTime has passed', async () => {
|
||||
// commitTime 259200
|
||||
await liquidPledging.donate(1, 2, {from: giver1, value: 1000, $extraGas: 50000});
|
||||
await liquidPledging.donate(1, 2, token.$address, 1000, { from: giver1 });
|
||||
// commitTime 86400
|
||||
await liquidPledging.donate(1, 3, {from: giver1, value: 1000, $extraGas: 50000});
|
||||
await liquidPledging.donate(1, 3, token.$address, 1000, { from: giver1 });
|
||||
// commitTime 0
|
||||
await liquidPledging.donate(6, 3, {from: giver2, value: 1000, $extraGas: 50000});
|
||||
await liquidPledging.donate(6, 3, token.$address, 1000, { from: giver2 });
|
||||
|
||||
// set the time
|
||||
const now = Math.floor(new Date().getTime() / 1000);
|
||||
await liquidPledging.setMockedTime(now);
|
||||
await liquidPledging.setMockedTime(now, { $extraGas: 200000 });
|
||||
|
||||
// delegate to project
|
||||
await liquidPledging.transfer(2, 2, 1000, 4, {from: delegate1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(3, 3, 1000, 4, {from: delegate2, $extraGas: 100000});
|
||||
await liquidPledging.transfer(3, 5, 1000, 4, {from: delegate2, $extraGas: 100000});
|
||||
await liquidPledging.transfer(2, 2, 1000, 4, { from: delegate1 });
|
||||
await liquidPledging.transfer(3, 3, 1000, 4, { from: delegate2 });
|
||||
await liquidPledging.transfer(3, 5, 1000, 4, { from: delegate2 });
|
||||
|
||||
// advance the time
|
||||
await liquidPledging.setMockedTime( now + 100000 );
|
||||
await liquidPledging.setMockedTime(now + 100000, { $extraGas: 200000 });
|
||||
|
||||
await liquidPledging.mNormalizePledge([6, 7, 8], {$extraGas: 100000});
|
||||
await liquidPledging.mNormalizePledge([6, 7, 8], { $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges.length, 11);
|
||||
|
@ -109,13 +124,13 @@ describe('NormalizePledge test', function () {
|
|||
});
|
||||
|
||||
it('Should transfer pledge to oldestPledgeNotCanceled', async () => {
|
||||
await liquidPledging.transfer(4, 10, 1000, 5, {from: adminProject1, $extraGas: 100000});
|
||||
await liquidPledging.transfer(4, 10, 1000, 5, { from: adminProject1, $extraGas: 200000 });
|
||||
|
||||
// cancel projects
|
||||
await liquidPledging.cancelProject(4, {from: adminProject1});
|
||||
await liquidPledging.cancelProject(5, {from: adminProject2});
|
||||
await liquidPledging.cancelProject(4, { from: adminProject1, $extraGas: 200000 });
|
||||
await liquidPledging.cancelProject(5, { from: adminProject2, $extraGas: 200000 });
|
||||
|
||||
await liquidPledging.mNormalizePledge([9, 11], {$extraGas: 100000});
|
||||
await liquidPledging.mNormalizePledge([9, 11], { $extraGas: 200000 });
|
||||
|
||||
const st = await liquidPledgingState.getState();
|
||||
assert.equal(st.pledges.length, 12);
|
||||
|
@ -123,5 +138,5 @@ describe('NormalizePledge test', function () {
|
|||
assert.equal(st.pledges[5].amount, 1000);
|
||||
assert.equal(st.pledges[9].amount, 0);
|
||||
assert.equal(st.pledges[11].amount, 0);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/* eslint-env mocha */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const TestRPC = require('ganache-cli');
|
||||
const Web3 = require('web3');
|
||||
const { assert } = require('chai');
|
||||
const { LPVault, LPFactory, LiquidPledgingState, Kernel, ACL, test } = require('../index');
|
||||
|
||||
const { StandardTokenTest, assertFail, LiquidPledgingMock } = test;
|
||||
|
||||
describe('Vault test', function() {
|
||||
this.timeout(0);
|
||||
|
||||
let testrpc;
|
||||
let web3;
|
||||
let accounts;
|
||||
let liquidPledging;
|
||||
let liquidPledgingState;
|
||||
let vault;
|
||||
let vaultOwner;
|
||||
let escapeHatchCaller;
|
||||
let escapeHatchDestination;
|
||||
let giver1;
|
||||
let adminProject1;
|
||||
let restrictedPaymentsConfirmer;
|
||||
let token;
|
||||
|
||||
before(async () => {
|
||||
testrpc = TestRPC.server({
|
||||
gasLimit: 6700000,
|
||||
total_accounts: 10,
|
||||
});
|
||||
|
||||
testrpc.listen(8545, '127.0.0.1');
|
||||
|
||||
web3 = new Web3('http://localhost:8545');
|
||||
accounts = await web3.eth.getAccounts();
|
||||
giver1 = accounts[1];
|
||||
adminProject1 = accounts[2];
|
||||
vaultOwner = accounts[3];
|
||||
escapeHatchDestination = accounts[4];
|
||||
escapeHatchCaller = accounts[5];
|
||||
restrictedPaymentsConfirmer = accounts[6];
|
||||
});
|
||||
|
||||
after(done => {
|
||||
testrpc.close();
|
||||
done();
|
||||
});
|
||||
|
||||
it('Should deploy Vault contract', async function() {
|
||||
const baseVault = await LPVault.new(web3, escapeHatchDestination);
|
||||
const baseLP = await LiquidPledgingMock.new(web3, escapeHatchDestination);
|
||||
lpFactory = await LPFactory.new(web3, baseVault.$address, baseLP.$address);
|
||||
|
||||
const r = await lpFactory.newLP(accounts[0], escapeHatchDestination);
|
||||
|
||||
const vaultAddress = r.events.DeployVault.returnValues.vault;
|
||||
vault = new LPVault(web3, vaultAddress);
|
||||
|
||||
const lpAddress = r.events.DeployLiquidPledging.returnValues.liquidPledging;
|
||||
liquidPledging = new LiquidPledgingMock(web3, lpAddress);
|
||||
|
||||
liquidPledgingState = new LiquidPledgingState(liquidPledging);
|
||||
|
||||
// set permissions
|
||||
const kernel = new Kernel(web3, await liquidPledging.kernel());
|
||||
acl = new ACL(web3, await kernel.acl());
|
||||
await acl.createPermission(
|
||||
accounts[0],
|
||||
vault.$address,
|
||||
await vault.CANCEL_PAYMENT_ROLE(),
|
||||
accounts[0],
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
await acl.createPermission(
|
||||
accounts[0],
|
||||
vault.$address,
|
||||
await vault.CONFIRM_PAYMENT_ROLE(),
|
||||
accounts[0],
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
await acl.grantPermission(
|
||||
escapeHatchCaller,
|
||||
vault.$address,
|
||||
await vault.ESCAPE_HATCH_CALLER_ROLE(),
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
await acl.revokePermission(
|
||||
accounts[0],
|
||||
vault.$address,
|
||||
await vault.ESCAPE_HATCH_CALLER_ROLE(),
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
|
||||
await liquidPledging.addGiver('Giver1', '', 0, '0x0', { from: giver1, $extraGas: 100000 });
|
||||
await liquidPledging.addProject('Project1', '', adminProject1, 0, 0, '0x0', {
|
||||
from: adminProject1,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
|
||||
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
|
||||
assert.equal(nAdmins, 2);
|
||||
|
||||
token = await StandardTokenTest.new(web3);
|
||||
await token.mint(giver1, web3.utils.toWei('1000'));
|
||||
await token.approve(liquidPledging.$address, '0xFFFFFFFFFFFFFFFF', { from: giver1 });
|
||||
});
|
||||
|
||||
it('Should hold funds from liquidPledging', async function() {
|
||||
await liquidPledging.addGiverAndDonate(2, token.$address, 10000, {
|
||||
from: giver1,
|
||||
$extraGas: 100000,
|
||||
});
|
||||
|
||||
const balance = await token.balanceOf(vault.$address);
|
||||
assert.equal(10000, balance);
|
||||
});
|
||||
|
||||
it('escapeFunds should fail', async function() {
|
||||
// only vaultOwner can escapeFunds
|
||||
await assertFail(vault.escapeFunds(0x0, 1000, { gas: 4000000 }));
|
||||
|
||||
// can't send more then the balance
|
||||
await assertFail(vault.escapeFunds(0x0, 11000, { from: vaultOwner, gas: 4000000 }));
|
||||
});
|
||||
|
||||
it('escapeFunds should send funds to escapeHatchDestination', async function() {
|
||||
const preBalance = await token.balanceOf(escapeHatchDestination);
|
||||
|
||||
await assertFail(vault.escapeFunds(0x0, 1000, { from: escapeHatchCaller, gas: 1000000 }));
|
||||
|
||||
await vault.escapeFunds(token.$address, 1000, { from: escapeHatchCaller, $extraGas: 200000 });
|
||||
|
||||
const vaultBalance = await token.balanceOf(vault.$address);
|
||||
assert.equal(9000, vaultBalance);
|
||||
|
||||
const expected = web3.utils
|
||||
.toBN(preBalance)
|
||||
.add(web3.utils.toBN('1000'))
|
||||
.toString();
|
||||
const postBalance = await token.balanceOf(escapeHatchDestination);
|
||||
|
||||
assert.equal(expected, postBalance);
|
||||
|
||||
await token.transfer(vault.$address, 1000, { from: escapeHatchDestination, $extraGas: 200000 });
|
||||
});
|
||||
|
||||
it('should restrict confirm payment to payments under specified amount', async function() {
|
||||
await liquidPledging.withdraw(2, 300, { from: adminProject1, $extraGas: 200000 });
|
||||
await liquidPledging.withdraw(2, 700, { from: adminProject1, $extraGas: 200000 });
|
||||
|
||||
// set permission for 2nd param (p.amount) <= 300
|
||||
await acl.grantPermissionP(
|
||||
restrictedPaymentsConfirmer,
|
||||
vault.$address,
|
||||
await vault.CONFIRM_PAYMENT_ROLE(),
|
||||
['0x010600000000000000000000000000000000000000000000000000000000012c'],
|
||||
{ $extraGas: 200000 },
|
||||
);
|
||||
|
||||
await assertFail(vault.confirmPayment(1, { from: restrictedPaymentsConfirmer, gas: 4000000 }));
|
||||
await vault.confirmPayment(0, { from: restrictedPaymentsConfirmer, $extraGas: 200000 });
|
||||
});
|
||||
});
|
|
@ -1,12 +1,17 @@
|
|||
const chai = require('chai');
|
||||
const assert = chai.assert;
|
||||
|
||||
module.exports = async function(callback) {
|
||||
module.exports = async function(promise) {
|
||||
let web3_error_thrown = false;
|
||||
try {
|
||||
await callback();
|
||||
await promise
|
||||
.then(({ status }) => {
|
||||
if (status === '0x00') {
|
||||
web3_error_thrown = true;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes("invalid opcode")) web3_error_thrown = true;
|
||||
if (error.message.includes("invalid opcode") || error.message.includes('revert')) web3_error_thrown = true;
|
||||
}
|
||||
assert.ok(web3_error_thrown, "Transaction should fail");
|
||||
};
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
let bNumber;
|
||||
module.exports = (gasUsage) => (res) => {
|
||||
if (res.includes('Block Number: ')) {
|
||||
bNumber= res.split('Block Number: ')[1];
|
||||
} else if (res.includes('Gas usage: ')) {
|
||||
const g = res.split('Gas usage: ')[1];
|
||||
gasUsage[bNumber] = g;
|
||||
} else if (res.includes('Runtime Error: ')) {
|
||||
// need to subtract 1 b/c gas gets printed before block number, so we are always 1 block behind
|
||||
delete gasUsage[bNumber - 1];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue