Merge branch 'aragon'

This commit is contained in:
perissology 2018-05-23 07:43:02 -07:00
commit 24ec03a4fe
36 changed files with 11396 additions and 3829 deletions

2
.gitignore vendored
View File

@ -1,3 +1,4 @@
build/
lib/
node_modules/
.DS_Store
@ -8,4 +9,3 @@ __pycache__
.idea
*.iml
build/

View File

@ -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,

View File

@ -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:
```

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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");
}

63
contracts/LPFactory.sol Normal file
View File

@ -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));
}
}

View File

@ -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
);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

360
contracts/PledgeAdmins.sol Normal file
View File

@ -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;
}
}

158
contracts/Pledges.sol Normal file
View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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,
),
},
};

View File

@ -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);

View File

@ -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);

View File

@ -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;
});
}

View File

@ -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);

135
lib/liquidPledgingState.js Normal file
View File

@ -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;

4638
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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);
});
});

View File

@ -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 }));
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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);
})
});
});

164
test/Vault.js Normal file
View File

@ -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 });
});
});

View File

@ -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");
};

12
test/helpers/gasLogger.js Normal file
View File

@ -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];
}
}

6341
yarn.lock Normal file

File diff suppressed because it is too large Load Diff