allow proxy donations

fix bug where eth could be burned to 0 pledge
This commit is contained in:
perissology 2018-02-13 01:24:19 +01:00
parent 4cfebbc5fb
commit 6c8355e346
8 changed files with 262 additions and 222 deletions

View File

@ -1,4 +1,4 @@
pragma solidity ^0.4.15;
pragma solidity ^0.4.18;
/*
Copyright 2016, Jordi Baylina
Contributor: Adrià Massanet <adria@codecontext.io>
@ -22,8 +22,8 @@ import "giveth-common-contracts/contracts/ERC20.sol";
import "@aragon/os/contracts/apps/AragonApp.sol";
/// @dev `Escapable` is a base level contract built off of the `Owned`
/// contract; it creates an escape hatch function that can be called in an
/// @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
@ -31,6 +31,9 @@ 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
@ -45,23 +48,6 @@ contract EscapableApp is AragonApp {
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);
}
/// @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) constant public returns (bool) {
return !escapeBlacklist[_token];
}
/// @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
@ -84,6 +70,20 @@ contract EscapableApp is AragonApp {
EscapeHatchCalled(_token, balance);
}
event EscapeHatchBlackistedToken(address token);
event EscapeHatchCalled(address token, uint amount);
/// @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) constant public returns (bool) {
return !escapeBlacklist[_token];
}
/// @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

@ -1,4 +1,4 @@
pragma solidity ^0.4.11;
pragma solidity ^0.4.18;
/*
Copyright 2017, Jordi Baylina
@ -87,6 +87,7 @@ contract LPVault is EscapableApp {
function initialize(address _escapeHatchDestination) onlyInit public {
require(false); // overload the EscapableApp
_escapeHatchDestination;
}
/// @param _liquidPledging
@ -110,7 +111,7 @@ contract LPVault is EscapableApp {
/// @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) external authP(SET_AUTOPAY_ROLE, ar(_automatic)) {
function setAutopay(bool _automatic) external authP(SET_AUTOPAY_ROLE, _arr(_automatic)) {
autoPay = _automatic;
AutoPaySet(autoPay);
}
@ -138,7 +139,7 @@ contract LPVault is EscapableApp {
AuthorizePayment(idPayment, _ref, _dest, _amount);
if (autoPay) {
doConfirmPayment(idPayment);
_doConfirmPayment(idPayment);
}
return idPayment;
@ -150,21 +151,21 @@ contract LPVault is EscapableApp {
/// has been authorized
/// @param _idPayment Array lookup for the payment.
function confirmPayment(uint _idPayment) public {
doConfirmPayment(_idPayment);
_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) public {
doCancelPayment(_idPayment);
_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++) {
doConfirmPayment(_idPayments[i]);
_doConfirmPayment(_idPayments[i]);
}
}
@ -172,7 +173,7 @@ contract LPVault is EscapableApp {
/// @param _idPayments An array of multiple payment ids
function multiCancel(uint[] _idPayments) external {
for (uint i = 0; i < _idPayments.length; i++) {
doCancelPayment(_idPayments[i]);
_doCancelPayment(_idPayments[i]);
}
}
@ -205,7 +206,7 @@ contract LPVault is EscapableApp {
/// @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);
@ -221,7 +222,7 @@ contract LPVault is EscapableApp {
/// @notice Cancels a pending payment (internal function)
/// @param _idPayment id number for the payment
function doCancelPayment(uint _idPayment) internal authP(CANCEL_PAYMENT_ROLE, arr(_idPayment)) {
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);
@ -233,7 +234,7 @@ contract LPVault is EscapableApp {
CancelPayment(_idPayment, p.ref);
}
function ar(bool a) internal pure returns (uint256[] r) {
function _arr(bool a) internal pure returns (uint256[] r) {
r = new uint256[](1);
uint _a;
assembly {

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
@ -27,6 +27,21 @@ import "./LiquidPledgingBase.sol";
/// to allow for expanded functionality.
contract LiquidPledging is LiquidPledgingBase {
function addGiverAndDonate(uint64 idReceiver)
public payable
{
addGiverAndDonate(idReceiver, msg.sender);
}
function addGiverAndDonate(uint64 idReceiver, address donorAddress)
public payable
{
require(donorAddress != 0);
// default to a 3 day (259200 seconds) commitTime
uint64 idGiver = _addGiver(donorAddress, "", "", 259200, ILiquidPledgingPlugin(0));
donate(idGiver, idReceiver);
}
/// @notice This is how value enters the system and how pledges are created;
/// the ether is sent to the vault, an pledge for the Giver is created (or
/// found), the amount of ETH donated in wei is added to the `amount` in
@ -38,14 +53,9 @@ contract LiquidPledging is LiquidPledgingBase {
function donate(uint64 idGiver, uint64 idReceiver)
public payable
{
if (idGiver == 0) {
// default to a 3 day (259200 seconds) commitTime
idGiver = uint64(addGiver("", "", 259200, ILiquidPledgingPlugin(0x0)));
}
require(idGiver > 0); // prevent burning donations. idReceiver is checked in _transfer
PledgeAdmins.PledgeAdmin storage sender = _findAdmin(idGiver);
require(sender.adminType == PledgeAdminType.Giver);
require(canPerform(msg.sender, PLEDGE_ADMIN_ROLE, arr(uint(idGiver))));
uint amount = msg.value;
require(amount > 0);
@ -66,9 +76,9 @@ contract LiquidPledging is LiquidPledgingBase {
Pledges.Pledge storage pTo = _findPledge(idPledge);
pTo.amount += amount;
Transfer(0, idPledge, amount); // An event
Transfer(0, idPledge, amount);
transfer(idGiver, idPledge, amount, idReceiver); // LP accounting
_transfer(idGiver, idPledge, amount, idReceiver);
}
/// @notice Transfers amounts between pledges for internal accounting
@ -87,121 +97,7 @@ contract LiquidPledging is LiquidPledgingBase {
uint64 idReceiver
) authP(PLEDGE_ADMIN_ROLE, arr(uint(idSender), amount)) public
{
idPledge = normalizePledge(idPledge);
Pledges.Pledge storage p = _findPledge(idPledge);
PledgeAdmins.PledgeAdmin storage receiver = _findAdmin(idReceiver);
require(p.pledgeState == PledgeState.Pledged);
// If the sender is the owner of the Pledge
if (p.owner == idSender) {
if (receiver.adminType == PledgeAdmins.PledgeAdminType.Giver) {
_transferOwnershipToGiver(idPledge, amount, idReceiver);
} else if (receiver.adminType == PledgeAdmins.PledgeAdminType.Project) {
_transferOwnershipToProject(idPledge, amount, idReceiver);
} else if (receiver.adminType == PledgeAdmins.PledgeAdminType.Delegate) {
uint recieverDIdx = _getDelegateIdx(p, idReceiver);
if (p.intendedProject > 0 && recieverDIdx != NOTFOUND) {
// if there is an intendedProject and the receiver is in the delegationChain,
// then we want to preserve the delegationChain as this is a veto of the
// intendedProject by the owner
if (recieverDIdx == p.delegationChain.length - 1) {
uint64 toPledge = _findOrCreatePledge(
p.owner,
p.delegationChain,
0,
0,
p.oldPledge,
Pledges.PledgeState.Pledged);
_doTransfer(idPledge, toPledge, amount);
} else {
_undelegate(idPledge, amount, p.delegationChain.length - receiverDIdx - 1);
}
} else {
// owner is not vetoing an intendedProject and is transferring the pledge to a delegate,
// so we want to reset the delegationChain
idPledge = _undelegate(
idPledge,
amount,
p.delegationChain.length
);
_appendDelegate(idPledge, amount, idReceiver);
}
} 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 == PledgeAdmins.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 == PledgeAdmins.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 == PledgeAdmins.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
_transfer(idSender, idPledge, amount, idReceiver);
}
/// @notice Authorizes a payment be made from the `vault` can be used by the
@ -388,61 +284,4 @@ contract LiquidPledging is LiquidPledgingBase {
normalizePledge( pledges[i] );
}
}
/// @notice Only affects pledges with the Pledged Pledges.PledgeState for 2 things:
/// #1: Checks if the pledge should be committed. This means that
/// if the pledge has an intendedProject and it is past the
/// commitTime, it changes the owner to be the proposed project
/// (The UI will have to read the commit time and manually do what
/// this function does to the pledge for the end user
/// at the expiration of the commitTime)
///
/// #2: Checks to make sure that if there has been a cancellation in the
/// chain of projects, the pledge's owner has been changed
/// appropriately.
///
/// This function can be called by anybody at anytime on any pledge.
/// In general it can be called to force the calls of the affected
/// plugins, which also need to be predicted by the UI
/// @param idPledge This is the id of the pledge that will be normalized
/// @return The normalized Pledge!
function normalizePledge(uint64 idPledge) public returns(uint64) {
Pledges.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 != Pledges.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,
Pledges.PledgeState.Pledged
);
uint64 toPledge = _findOrCreatePledge(
p.intendedProject,
new uint64[](0),
0,
0,
oldPledge,
Pledges.PledgeState.Pledged
);
_doTransfer(idPledge, toPledge, p.amount);
idPledge = toPledge;
p = _findPledge(idPledge);
}
toPledge = _getOldestPledgeNotCanceled(idPledge);
if (toPledge != idPledge) {
_doTransfer(idPledge, toPledge, p.amount);
}
return toPledge;
}
}

View File

@ -1,4 +1,4 @@
pragma solidity ^0.4.11;
pragma solidity ^0.4.18;
/*
Copyright 2017, Jordi Baylina
@ -61,6 +61,7 @@ contract LiquidPledgingBase is PledgeAdmins, Pledges, EscapableApp {
function initialize(address _escapeHatchDestination) onlyInit public {
require(false); // overload the EscapableApp
_escapeHatchDestination;
}
/// @param _vault The vault where the ETH backing the pledges is stored
@ -98,10 +99,192 @@ contract LiquidPledgingBase is PledgeAdmins, Pledges, EscapableApp {
name = delegate.name;
}
/// @notice Only affects pledges with the Pledged Pledges.PledgeState for 2 things:
/// #1: Checks if the pledge should be committed. This means that
/// if the pledge has an intendedProject and it is past the
/// commitTime, it changes the owner to be the proposed project
/// (The UI will have to read the commit time and manually do what
/// this function does to the pledge for the end user
/// at the expiration of the commitTime)
///
/// #2: Checks to make sure that if there has been a cancellation in the
/// chain of projects, the pledge's owner has been changed
/// appropriately.
///
/// This function can be called by anybody at anytime on any pledge.
/// In general it can be called to force the calls of the affected
/// plugins, which also need to be predicted by the UI
/// @param idPledge This is the id of the pledge that will be normalized
/// @return The normalized Pledge!
function normalizePledge(uint64 idPledge) public returns(uint64) {
Pledges.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 != Pledges.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,
Pledges.PledgeState.Pledged
);
uint64 toPledge = _findOrCreatePledge(
p.intendedProject,
new uint64[](0),
0,
0,
oldPledge,
Pledges.PledgeState.Pledged
);
_doTransfer(idPledge, toPledge, p.amount);
idPledge = toPledge;
p = _findPledge(idPledge);
}
toPledge = _getOldestPledgeNotCanceled(idPledge);
if (toPledge != idPledge) {
_doTransfer(idPledge, toPledge, p.amount);
}
return toPledge;
}
////////////////////
// Internal methods
////////////////////
function _transfer(
uint64 idSender,
uint64 idPledge,
uint amount,
uint64 idReceiver
) internal
{
require(idReceiver > 0); // prevent burning value
idPledge = normalizePledge(idPledge);
Pledges.Pledge storage p = _findPledge(idPledge);
PledgeAdmins.PledgeAdmin storage receiver = _findAdmin(idReceiver);
require(p.pledgeState == PledgeState.Pledged);
// If the sender is the owner of the Pledge
if (p.owner == idSender) {
if (receiver.adminType == PledgeAdmins.PledgeAdminType.Giver) {
_transferOwnershipToGiver(idPledge, amount, idReceiver);
} else if (receiver.adminType == PledgeAdmins.PledgeAdminType.Project) {
_transferOwnershipToProject(idPledge, amount, idReceiver);
} else if (receiver.adminType == PledgeAdmins.PledgeAdminType.Delegate) {
uint recieverDIdx = _getDelegateIdx(p, idReceiver);
if (p.intendedProject > 0 && recieverDIdx != NOTFOUND) {
// if there is an intendedProject and the receiver is in the delegationChain,
// then we want to preserve the delegationChain as this is a veto of the
// intendedProject by the owner
if (recieverDIdx == p.delegationChain.length - 1) {
uint64 toPledge = _findOrCreatePledge(
p.owner,
p.delegationChain,
0,
0,
p.oldPledge,
Pledges.PledgeState.Pledged);
_doTransfer(idPledge, toPledge, amount);
} else {
_undelegate(idPledge, amount, p.delegationChain.length - receiverDIdx - 1);
}
} else {
// owner is not vetoing an intendedProject and is transferring the pledge to a delegate,
// so we want to reset the delegationChain
idPledge = _undelegate(
idPledge,
amount,
p.delegationChain.length
);
_appendDelegate(idPledge, amount, idReceiver);
}
} 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 == PledgeAdmins.PledgeAdminType.Giver) {
// Only transfer to the Giver who owns the pledge
assert(p.owner == idReceiver);
_undelegate(idPledge, amount, p.delegationChain.length);
return;
}
// And the receiver is another Delegate
if (receiver.adminType == PledgeAdmins.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 == PledgeAdmins.PledgeAdminType.Project) {
idPledge = _undelegate(
idPledge,
amount,
p.delegationChain.length - senderDIdx - 1
);
_proposeAssignProject(idPledge, amount, idReceiver);
return;
}
}
assert(false); // When the sender is not an owner or a delegate
}
/// @notice `transferOwnershipToProject` allows for the transfer of
/// ownership to the project, but it can also be called by a project
/// to un-delegate everyone by setting one's own id for the idReceiver

View File

@ -79,6 +79,23 @@ contract PledgeAdmins is AragonApp, LiquidPledgingPlugins {
uint64 commitTime,
ILiquidPledgingPlugin plugin
) public returns (uint64 idGiver)
{
return _addGiver(
msg.sender,
name,
url,
commitTime,
plugin
);
}
function _addGiver(
address addr,
string name,
string url,
uint64 commitTime,
ILiquidPledgingPlugin plugin
) public returns (uint64 idGiver)
{
require(isValidPlugin(plugin)); // Plugin check
@ -88,7 +105,7 @@ contract PledgeAdmins is AragonApp, LiquidPledgingPlugins {
admins.push(
PledgeAdmin(
PledgeAdminType.Giver,
msg.sender, // TODO: is this needed?
addr, // TODO: is this needed?
name,
url,
commitTime,
@ -340,7 +357,7 @@ contract PledgeAdmins is AragonApp, LiquidPledgingPlugins {
/// @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 returns (PledgeAdmin storage) {
function _findAdmin(uint64 idAdmin) internal view returns (PledgeAdmin storage) {
require(idAdmin < admins.length);
return admins[idAdmin];
}

View File

@ -65,7 +65,7 @@ describe('LiquidPledging cancelPledge normal scenario', function () {
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' });
await liquidPledging.addGiverAndDonate(1, { from: giver1, value: '1000' });
const nAdmins = await liquidPledging.numberOfPledgeAdmins();
assert.equal(nAdmins, 2);

View File

@ -335,7 +335,7 @@ 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'), $extraGas: 200000 });
await liquidPledging.addGiverAndDonate(1, { from: giver2, value: utils.toWei('1'), $extraGas: 200000 });
const nPledges = await liquidPledging.numberOfPledges();
assert.equal(utils.toDecimal(nPledges), utils.toDecimal(oldNPledges) + 1);
const nAdmins = await liquidPledging.numberOfPledgeAdmins();

View File

@ -79,7 +79,7 @@ describe('Vault test', function () {
});
it('Should hold funds from liquidPledging', async function () {
await liquidPledging.donate(0, 2, { from: giver1, value: 10000, $extraGas: 100000 });
await liquidPledging.addGiverAndDonate(2, { from: giver1, value: 10000, $extraGas: 100000 });
const balance = await web3.eth.getBalance(vault.$address);
assert.equal(10000, balance);