mirror of
https://github.com/status-im/liquid-funding.git
synced 2025-01-11 03:56:20 +00:00
34adef5505
This will eliminate the bug when pledge.oldPledge is a canceled project w/ a plugin and the plugin rejects transfers when canceled. This would prevent the pledge from being canceled. We address this by transfering the pledge amount to the oldestPledgeNotCanceled.
943 lines
33 KiB
Solidity
943 lines
33 KiB
Solidity
|
|
//File: contracts/ILiquidPledgingPlugin.sol
|
|
pragma solidity ^0.4.11;
|
|
|
|
contract ILiquidPledgingPlugin {
|
|
/// @notice Plugins are used (much like web hooks) to initiate an action
|
|
/// upon any donation, delegation, or transfer; this is an optional feature
|
|
/// and allows for extreme customization of the contract
|
|
/// @param context The situation that is triggering the plugin:
|
|
/// 0 -> Plugin for the owner transferring pledge to another party
|
|
/// 1 -> Plugin for the first delegate transferring pledge to another party
|
|
/// 2 -> Plugin for the second delegate transferring pledge to another party
|
|
/// ...
|
|
/// 255 -> Plugin for the intendedProject transferring pledge to another party
|
|
///
|
|
/// 256 -> Plugin for the owner receiving pledge to another party
|
|
/// 257 -> Plugin for the first delegate receiving pledge to another party
|
|
/// 258 -> Plugin for the second delegate receiving pledge to another party
|
|
/// ...
|
|
/// 511 -> Plugin for the intendedProject receiving pledge to another party
|
|
function beforeTransfer(
|
|
uint64 pledgeManager,
|
|
uint64 pledgeFrom,
|
|
uint64 pledgeTo,
|
|
uint64 context,
|
|
uint amount
|
|
) returns (uint maxAllowed);
|
|
function afterTransfer(
|
|
uint64 pledgeManager,
|
|
uint64 pledgeFrom,
|
|
uint64 pledgeTo,
|
|
uint64 context,
|
|
uint amount);
|
|
}
|
|
|
|
//File: contracts/LiquidPledgingBase.sol
|
|
pragma solidity ^0.4.11;
|
|
|
|
|
|
|
|
/// @dev This is declares a few functions from `Vault` so that the
|
|
/// `LiquidPledgingBase` contract can interface with the `Vault` contract
|
|
contract Vault {
|
|
function authorizePayment(bytes32 _ref, address _dest, uint _amount);
|
|
function () payable;
|
|
}
|
|
|
|
contract LiquidPledgingBase {
|
|
// Limits inserted to prevent large loops that could prevent canceling
|
|
uint constant MAX_DELEGATES = 20;
|
|
uint constant MAX_SUBPROJECT_LEVEL = 20;
|
|
uint constant MAX_INTERPROJECT_LEVEL = 20;
|
|
|
|
enum PledgeAdminType { Giver, Delegate, Project }
|
|
enum PaymentState { Pledged, Paying, Paid } // TODO name change Pledged
|
|
|
|
/// @dev This struct defines the details of each the PledgeAdmin, these
|
|
/// PledgeAdmins can own pledges and act as delegates
|
|
struct PledgeAdmin { // TODO name change PledgeAdmin
|
|
PledgeAdminType adminType; // Giver, Delegate or Project
|
|
address addr; // account or contract address for admin
|
|
string name;
|
|
string url;
|
|
uint64 commitTime; // In seconds, used for Givers' & Delegates' vetos
|
|
uint64 parentProject; // Only for projects
|
|
bool canceled; //Always false except for canceled projects
|
|
ILiquidPledgingPlugin plugin; // if the plugin is 0x0 then nothing happens if its a contract address than that smart contract is called via the milestone contract
|
|
}
|
|
|
|
struct Pledge {
|
|
uint amount;
|
|
uint64 owner; // PledgeAdmin
|
|
uint64[] delegationChain; // list of index numbers
|
|
uint64 intendedProject; // TODO change the name only used for when delegates are precommiting to a project
|
|
uint64 commitTime; // When the intendedProject will become the owner
|
|
uint64 oldPledge; // this points to the Pledge[] index that the Pledge was derived from
|
|
PaymentState paymentState;
|
|
}
|
|
|
|
Pledge[] pledges;
|
|
PledgeAdmin[] admins; //The list of pledgeAdmins 0 means there is no admin
|
|
Vault public vault;
|
|
|
|
// this mapping allows you to search for a specific pledge's index number by the hash of that pledge
|
|
mapping (bytes32 => uint64) hPledge2idx;//TODO Fix typo
|
|
|
|
|
|
/////
|
|
// Modifiers
|
|
/////
|
|
|
|
modifier onlyVault() {
|
|
require(msg.sender == address(vault));
|
|
_;
|
|
}
|
|
|
|
|
|
//////
|
|
// Constructor
|
|
//////
|
|
|
|
/// @notice The Constructor creates the `LiquidPledgingBase` on the blockchain
|
|
/// @param _vault Where the ETH is stored that the pledges represent
|
|
function LiquidPledgingBase(address _vault) {
|
|
admins.length = 1; // we reserve the 0 admin
|
|
pledges.length = 1; // we reserve the 0 pledge
|
|
vault = Vault(_vault);
|
|
}
|
|
|
|
|
|
///////
|
|
// Adminss functions
|
|
//////
|
|
|
|
/// @notice Creates a giver.
|
|
function addGiver(string name, string url, uint64 commitTime, ILiquidPledgingPlugin plugin
|
|
) returns (uint64 idGiver) {
|
|
|
|
idGiver = uint64(admins.length);
|
|
|
|
admins.push(PledgeAdmin(
|
|
PledgeAdminType.Giver,
|
|
msg.sender,
|
|
name,
|
|
url,
|
|
commitTime,
|
|
0,
|
|
false,
|
|
plugin));
|
|
|
|
GiverAdded(idGiver);
|
|
}
|
|
|
|
event GiverAdded(uint64 indexed idGiver);
|
|
|
|
///@notice Changes the address, name or commitTime associated with a specific giver
|
|
function updateGiver(
|
|
uint64 idGiver,
|
|
address newAddr,
|
|
string newName,
|
|
string newUrl,
|
|
uint64 newCommitTime)
|
|
{
|
|
PledgeAdmin storage giver = findAdmin(idGiver);
|
|
require(giver.adminType == PledgeAdminType.Giver); //Must be a Giver
|
|
require(giver.addr == msg.sender); //current addr had to originate this tx
|
|
giver.addr = newAddr;
|
|
giver.name = newName;
|
|
giver.url = newUrl;
|
|
giver.commitTime = newCommitTime;
|
|
GiverUpdated(idGiver);
|
|
}
|
|
|
|
event GiverUpdated(uint64 indexed idGiver);
|
|
|
|
/// @notice Creates a new Delegate
|
|
function addDelegate(string name, string url, uint64 commitTime, ILiquidPledgingPlugin plugin) returns (uint64 idDelegate) { //TODO return index number
|
|
|
|
idDelegate = uint64(admins.length);
|
|
|
|
admins.push(PledgeAdmin(
|
|
PledgeAdminType.Delegate,
|
|
msg.sender,
|
|
name,
|
|
url,
|
|
commitTime,
|
|
0,
|
|
false,
|
|
plugin));
|
|
|
|
DelegateAdded(idDelegate);
|
|
}
|
|
|
|
event DelegateAdded(uint64 indexed idDelegate);
|
|
|
|
///@notice Changes the address, name or commitTime associated with a specific delegate
|
|
function updateDelegate(
|
|
uint64 idDelegate,
|
|
address newAddr,
|
|
string newName,
|
|
string newUrl,
|
|
uint64 newCommitTime) {
|
|
PledgeAdmin storage delegate = findAdmin(idDelegate);
|
|
require(delegate.adminType == PledgeAdminType.Delegate);
|
|
require(delegate.addr == msg.sender);
|
|
delegate.addr = newAddr;
|
|
delegate.name = newName;
|
|
delegate.url = newUrl;
|
|
delegate.commitTime = newCommitTime;
|
|
DelegateUpdated(idDelegate);
|
|
}
|
|
|
|
event DelegateUpdated(uint64 indexed idDelegate);
|
|
|
|
/// @notice Creates a new Project
|
|
function addProject(string name, string url, address projectAdmin, uint64 parentProject, uint64 commitTime, ILiquidPledgingPlugin plugin) returns (uint64 idProject) {
|
|
if (parentProject != 0) {
|
|
PledgeAdmin storage pa = findAdmin(parentProject);
|
|
require(pa.adminType == PledgeAdminType.Project);
|
|
require(getProjectLevel(pa) < MAX_SUBPROJECT_LEVEL);
|
|
}
|
|
|
|
idProject = uint64(admins.length);
|
|
|
|
admins.push(PledgeAdmin(
|
|
PledgeAdminType.Project,
|
|
projectAdmin,
|
|
name,
|
|
url,
|
|
commitTime,
|
|
parentProject,
|
|
false,
|
|
plugin));
|
|
|
|
|
|
ProjectAdded(idProject);
|
|
}
|
|
|
|
event ProjectAdded(uint64 indexed idProject);
|
|
|
|
///@notice Changes the address, name or commitTime associated with a specific Project
|
|
function updateProject(
|
|
uint64 idProject,
|
|
address newAddr,
|
|
string newName,
|
|
string newUrl,
|
|
uint64 newCommitTime)
|
|
{
|
|
PledgeAdmin storage project = findAdmin(idProject);
|
|
require(project.adminType == PledgeAdminType.Project);
|
|
require(project.addr == msg.sender);
|
|
project.addr = newAddr;
|
|
project.name = newName;
|
|
project.url = newUrl;
|
|
project.commitTime = newCommitTime;
|
|
ProjectUpdated(idProject);
|
|
}
|
|
|
|
event ProjectUpdated(uint64 indexed idAdmin);
|
|
|
|
|
|
//////////
|
|
// Public constant functions
|
|
//////////
|
|
|
|
/// @notice Public constant that states how many pledgess are in the system
|
|
function numberOfPledges() constant returns (uint) {
|
|
return pledges.length - 1;
|
|
}
|
|
/// @notice Public constant that states the details of the specified Pledge
|
|
function getPledge(uint64 idPledge) constant returns(
|
|
uint amount,
|
|
uint64 owner,
|
|
uint64 nDelegates,
|
|
uint64 intendedProject,
|
|
uint64 commitTime,
|
|
uint64 oldPledge,
|
|
PaymentState paymentState
|
|
) {
|
|
Pledge storage n = findPledge(idPledge);
|
|
amount = n.amount;
|
|
owner = n.owner;
|
|
nDelegates = uint64(n.delegationChain.length);
|
|
intendedProject = n.intendedProject;
|
|
commitTime = n.commitTime;
|
|
oldPledge = n.oldPledge;
|
|
paymentState = n.paymentState;
|
|
}
|
|
/// @notice Public constant that states the delegates one by one, because
|
|
/// an array cannot be returned
|
|
function getPledgeDelegate(uint64 idPledge, uint idxDelegate) constant returns(
|
|
uint64 idDelegate,
|
|
address addr,
|
|
string name
|
|
) {
|
|
Pledge storage n = findPledge(idPledge);
|
|
idDelegate = n.delegationChain[idxDelegate - 1];
|
|
PledgeAdmin storage delegate = findAdmin(idDelegate);
|
|
addr = delegate.addr;
|
|
name = delegate.name;
|
|
}
|
|
/// @notice Public constant that states the number of admins in the system
|
|
function numberOfPledgeAdmins() constant returns(uint) {
|
|
return admins.length - 1;
|
|
}
|
|
/// @notice Public constant that states the details of the specified admin
|
|
function getPledgeAdmin(uint64 idAdmin) constant returns (
|
|
PledgeAdminType adminType,
|
|
address addr,
|
|
string name,
|
|
string url,
|
|
uint64 commitTime,
|
|
uint64 parentProject,
|
|
bool canceled,
|
|
address plugin)
|
|
{
|
|
PledgeAdmin storage m = findAdmin(idAdmin);
|
|
adminType = m.adminType;
|
|
addr = m.addr;
|
|
name = m.name;
|
|
url = m.url;
|
|
commitTime = m.commitTime;
|
|
parentProject = m.parentProject;
|
|
canceled = m.canceled;
|
|
plugin = address(m.plugin);
|
|
}
|
|
|
|
////////
|
|
// Private methods
|
|
///////
|
|
|
|
/// @notice All pledges technically exist... but if the pledge hasn't been
|
|
/// created in this system yet then it wouldn't be in the hash array
|
|
/// hPledge2idx[]; this creates a Pledge with and amount of 0 if one is not
|
|
/// created already...
|
|
function findOrCreatePledge(
|
|
uint64 owner,
|
|
uint64[] delegationChain,
|
|
uint64 intendedProject,
|
|
uint64 commitTime,
|
|
uint64 oldPledge,
|
|
PaymentState paid
|
|
) internal returns (uint64)
|
|
{
|
|
bytes32 hPledge = sha3(owner, delegationChain, intendedProject, commitTime, oldPledge, paid);
|
|
uint64 idx = hPledge2idx[hPledge];
|
|
if (idx > 0) return idx;
|
|
idx = uint64(pledges.length);
|
|
hPledge2idx[hPledge] = idx;
|
|
pledges.push(Pledge(0, owner, delegationChain, intendedProject, commitTime, oldPledge, paid));
|
|
return idx;
|
|
}
|
|
|
|
function findAdmin(uint64 idAdmin) internal returns (PledgeAdmin storage) {
|
|
require(idAdmin < admins.length);
|
|
return admins[idAdmin];
|
|
}
|
|
|
|
function findPledge(uint64 idPledge) internal returns (Pledge storage) {
|
|
require(idPledge < pledges.length);
|
|
return pledges[idPledge];
|
|
}
|
|
|
|
// a constant for the case that a delegate is requested that is not a delegate in the system
|
|
uint64 constant NOTFOUND = 0xFFFFFFFFFFFFFFFF;
|
|
|
|
// helper function that searches the delegationChain fro a specific delegate and
|
|
// level of delegation returns their idx in the delegation chain which reflect their level of authority
|
|
function getDelegateIdx(Pledge n, uint64 idDelegate) internal returns(uint64) {
|
|
for (uint i=0; i<n.delegationChain.length; i++) {
|
|
if (n.delegationChain[i] == idDelegate) return uint64(i);
|
|
}
|
|
return NOTFOUND;
|
|
}
|
|
|
|
// helper function that returns the pledge level solely to check that transfers
|
|
// between Projects not violate MAX_INTERPROJECT_LEVEL
|
|
function getPledgeLevel(Pledge n) internal returns(uint) {
|
|
if (n.oldPledge == 0) return 0; //changed
|
|
Pledge storage oldN = findPledge(n.oldPledge);
|
|
return getPledgeLevel(oldN) + 1;
|
|
}
|
|
|
|
// helper function that returns the max commit time of the owner and all the
|
|
// delegates
|
|
function maxCommitTime(Pledge n) internal returns(uint commitTime) {
|
|
PledgeAdmin storage m = findAdmin(n.owner);
|
|
commitTime = m.commitTime;
|
|
|
|
for (uint i=0; i<n.delegationChain.length; i++) {
|
|
m = findAdmin(n.delegationChain[i]);
|
|
if (m.commitTime > commitTime) commitTime = m.commitTime;
|
|
}
|
|
}
|
|
|
|
// helper function that returns the project level solely to check that there
|
|
// are not too many Projects that violate MAX_SUBCAMPAIGNS_LEVEL
|
|
function getProjectLevel(PledgeAdmin m) internal returns(uint) {
|
|
assert(m.adminType == PledgeAdminType.Project);
|
|
if (m.parentProject == 0) return(1);
|
|
PledgeAdmin storage parentNM = findAdmin(m.parentProject);
|
|
return getProjectLevel(parentNM);
|
|
}
|
|
|
|
function isProjectCanceled(uint64 projectId) constant returns (bool) {
|
|
PledgeAdmin storage m = findAdmin(projectId);
|
|
if (m.adminType == PledgeAdminType.Giver) return false;
|
|
assert(m.adminType == PledgeAdminType.Project);
|
|
if (m.canceled) return true;
|
|
if (m.parentProject == 0) return false;
|
|
return isProjectCanceled(m.parentProject);
|
|
}
|
|
|
|
// @notice A helper function for canceling projects
|
|
// @param idPledge the pledge that may or may not be canceled
|
|
function getOldestPledgeNotCanceled(uint64 idPledge) internal constant returns(uint64) { //todo rename
|
|
if (idPledge == 0) return 0;
|
|
Pledge storage n = findPledge(idPledge);
|
|
PledgeAdmin storage admin = findAdmin(n.owner);
|
|
if (admin.adminType == PledgeAdminType.Giver) return idPledge;
|
|
|
|
assert(admin.adminType == PledgeAdminType.Project);
|
|
|
|
if (!isProjectCanceled(n.owner)) return idPledge;
|
|
|
|
return getOldestPledgeNotCanceled(n.oldPledge);
|
|
}
|
|
|
|
function checkAdminOwner(PledgeAdmin m) internal constant {
|
|
require((msg.sender == m.addr) || (msg.sender == address(m.plugin)));
|
|
}
|
|
}
|
|
|
|
//File: contracts/LiquidPledging.sol
|
|
pragma solidity ^0.4.11;
|
|
|
|
|
|
|
|
|
|
contract LiquidPledging is LiquidPledgingBase {
|
|
|
|
|
|
//////
|
|
// Constructor
|
|
//////
|
|
|
|
// This constructor also calls the constructor for `LiquidPledgingBase`
|
|
function LiquidPledging(address _vault) LiquidPledgingBase(_vault) {
|
|
}
|
|
|
|
/// @notice This is how value enters into the system which creates pledges;
|
|
/// the token of value goes into the vault and the amount in the pledge
|
|
/// relevant to this Giver without delegates is increased, and a normal
|
|
/// transfer is done to the idReceiver
|
|
/// @param idGiver Identifier of the giver thats donating.
|
|
/// @param idReceiver To whom it's transfered. Can be the same giver, another
|
|
/// giver, a delegate or a project
|
|
|
|
function donate(uint64 idGiver, uint64 idReceiver) payable {
|
|
if (idGiver == 0) {
|
|
idGiver = addGiver('', '', 259200, ILiquidPledgingPlugin(0x0)); // default to 3 day commitTime
|
|
}
|
|
|
|
PledgeAdmin storage sender = findAdmin(idGiver);
|
|
|
|
checkAdminOwner(sender);
|
|
|
|
require(sender.adminType == PledgeAdminType.Giver);
|
|
|
|
uint amount = msg.value;
|
|
|
|
require(amount > 0);
|
|
|
|
vault.transfer(amount); // transfers the baseToken to the Vault
|
|
uint64 idPledge = findOrCreatePledge(
|
|
idGiver,
|
|
new uint64[](0), //what is new?
|
|
0,
|
|
0,
|
|
0,
|
|
PaymentState.Pledged);
|
|
|
|
|
|
Pledge storage nTo = findPledge(idPledge);
|
|
nTo.amount += amount;
|
|
|
|
Transfer(0, idPledge, amount);
|
|
|
|
transfer(idGiver, idPledge, amount, idReceiver);
|
|
}
|
|
|
|
|
|
/// @notice Moves value between pledges
|
|
/// @param idSender ID of the giver, delegate or project admin that is transferring
|
|
/// the funds from Pledge to Pledge. This admin must have permissions to move the value
|
|
/// @param idPledge Id of the pledge that's moving the value
|
|
/// @param amount Quantity of value that's being moved
|
|
/// @param idReceiver Destination of the value, can be a giver sending to a giver or
|
|
/// a delegate, a delegate to another delegate or a project to precommit it to that project
|
|
function transfer(uint64 idSender, uint64 idPledge, uint amount, uint64 idReceiver) {
|
|
|
|
idPledge = normalizePledge(idPledge);
|
|
|
|
Pledge storage n = findPledge(idPledge);
|
|
PledgeAdmin storage receiver = findAdmin(idReceiver);
|
|
PledgeAdmin storage sender = findAdmin(idSender);
|
|
|
|
checkAdminOwner(sender);
|
|
require(n.paymentState == PaymentState.Pledged);
|
|
|
|
// If the sender is the owner
|
|
if (n.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, n.delegationChain.length);
|
|
appendDelegate(idPledge, amount, idReceiver);
|
|
} else {
|
|
assert(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If the sender is a delegate
|
|
uint senderDIdx = getDelegateIdx(n, idSender);
|
|
if (senderDIdx != NOTFOUND) {
|
|
|
|
// If the receiver is another giver
|
|
if (receiver.adminType == PledgeAdminType.Giver) {
|
|
// Only accept to change to the original giver to remove all delegates
|
|
assert(n.owner == idReceiver);
|
|
undelegate(idPledge, amount, n.delegationChain.length);
|
|
return;
|
|
}
|
|
|
|
// If the receiver is another delegate
|
|
if (receiver.adminType == PledgeAdminType.Delegate) {
|
|
uint receiverDIdx = getDelegateIdx(n, idReceiver);
|
|
|
|
// If the receiver is not in the delegate list
|
|
if (receiverDIdx == NOTFOUND) {
|
|
idPledge = undelegate(idPledge, amount, n.delegationChain.length - senderDIdx - 1);
|
|
appendDelegate(idPledge, amount, idReceiver);
|
|
|
|
// If the receiver is already part of the delegate chain 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 delegation chain
|
|
} else if (receiverDIdx > senderDIdx) {
|
|
idPledge = undelegate(idPledge, amount, n.delegationChain.length - senderDIdx - 1);
|
|
appendDelegate(idPledge, amount, idReceiver);
|
|
|
|
// If the receiver is already part of the delegate chain and is
|
|
// before the sender, then the sender and all of the other
|
|
// delegates after the RECEIVER are revomved from the chain,
|
|
// this is interesting because the delegate undelegates from the
|
|
// delegates that delegated to this delegate... game theory issues? should this be allowed
|
|
} else if (receiverDIdx <= senderDIdx) {
|
|
undelegate(idPledge, amount, n.delegationChain.length - receiverDIdx -1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If the delegate wants to support a project, they undelegate all
|
|
// the delegates after them in the chain and choose a project
|
|
if (receiver.adminType == PledgeAdminType.Project) {
|
|
idPledge = undelegate(idPledge, amount, n.delegationChain.length - senderDIdx - 1);
|
|
proposeAssignProject(idPledge, amount, idReceiver);
|
|
return;
|
|
}
|
|
}
|
|
assert(false); // It is not the owner nor any delegate.
|
|
}
|
|
|
|
|
|
/// @notice This method is used to withdraw value from the system. This can be used
|
|
/// by the givers to avoid committing the donation or by project admin to use
|
|
/// the Ether.
|
|
/// @param idPledge Id of the pledge that wants to be withdrawn.
|
|
/// @param amount Quantity of Ether that wants to be withdrawn.
|
|
function withdraw(uint64 idPledge, uint amount) {
|
|
|
|
idPledge = normalizePledge(idPledge);
|
|
|
|
Pledge storage n = findPledge(idPledge);
|
|
|
|
require(n.paymentState == PaymentState.Pledged);
|
|
|
|
PledgeAdmin storage owner = findAdmin(n.owner);
|
|
|
|
checkAdminOwner(owner);
|
|
|
|
uint64 idNewPledge = findOrCreatePledge(
|
|
n.owner,
|
|
n.delegationChain,
|
|
0,
|
|
0,
|
|
n.oldPledge,
|
|
PaymentState.Paying
|
|
);
|
|
|
|
doTransfer(idPledge, idNewPledge, amount);
|
|
|
|
vault.authorizePayment(bytes32(idNewPledge), owner.addr, amount);
|
|
}
|
|
|
|
/// @notice Method called by the vault to confirm a payment.
|
|
/// @param idPledge Id of the pledge that wants to be withdrawn.
|
|
/// @param amount Quantity of Ether that wants to be withdrawn.
|
|
function confirmPayment(uint64 idPledge, uint amount) onlyVault {
|
|
Pledge storage n = findPledge(idPledge);
|
|
|
|
require(n.paymentState == PaymentState.Paying);
|
|
|
|
// Check the project is not canceled in the while.
|
|
require(getOldestPledgeNotCanceled(idPledge) == idPledge);
|
|
|
|
uint64 idNewPledge = findOrCreatePledge(
|
|
n.owner,
|
|
n.delegationChain,
|
|
0,
|
|
0,
|
|
n.oldPledge,
|
|
PaymentState.Paid
|
|
);
|
|
|
|
doTransfer(idPledge, idNewPledge, amount);
|
|
}
|
|
|
|
/// @notice Method called by the vault to cancel a payment.
|
|
/// @param idPledge Id of the pledge that wants to be canceled for withdraw.
|
|
/// @param amount Quantity of Ether that wants to be rolled back.
|
|
function cancelPayment(uint64 idPledge, uint amount) onlyVault {
|
|
Pledge storage n = findPledge(idPledge);
|
|
|
|
require(n.paymentState == PaymentState.Paying); //TODO change to revert
|
|
|
|
// When a payment is canceled, never is assigned to a project.
|
|
uint64 oldPledge = findOrCreatePledge(
|
|
n.owner,
|
|
n.delegationChain,
|
|
0,
|
|
0,
|
|
n.oldPledge,
|
|
PaymentState.Pledged
|
|
);
|
|
|
|
oldPledge = normalizePledge(oldPledge);
|
|
|
|
doTransfer(idPledge, oldPledge, amount);
|
|
}
|
|
|
|
/// @notice Method called to cancel this project.
|
|
/// @param idProject Id of the projct that wants to be canceled.
|
|
function cancelProject(uint64 idProject) {
|
|
PledgeAdmin storage project = findAdmin(idProject);
|
|
checkAdminOwner(project);
|
|
project.canceled = true;
|
|
|
|
CancelProject(idProject);
|
|
}
|
|
|
|
|
|
function cancelPledge(uint64 idPledge, uint amount) {
|
|
idPledge = normalizePledge(idPledge);
|
|
|
|
Pledge storage n = findPledge(idPledge);
|
|
require(n.oldPledge != 0);
|
|
|
|
PledgeAdmin storage m = findAdmin(n.owner);
|
|
checkAdminOwner(m);
|
|
|
|
uint64 oldPledge = getOldestPledgeNotCanceled(n.oldPledge);
|
|
doTransfer(idPledge, oldPledge, amount);
|
|
}
|
|
|
|
|
|
////////
|
|
// Multi pledge methods
|
|
////////
|
|
|
|
// This set of functions makes moving a lot of pledges around much more
|
|
// efficient (saves gas) than calling these functions in series
|
|
uint constant D64 = 0x10000000000000000;
|
|
function mTransfer(uint64 idSender, uint[] pledgesAmounts, uint64 idReceiver) {
|
|
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
|
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
|
uint amount = pledgesAmounts[i] / D64;
|
|
|
|
transfer(idSender, idPledge, amount, idReceiver);
|
|
}
|
|
}
|
|
|
|
function mWithdraw(uint[] pledgesAmounts) {
|
|
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
|
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
|
uint amount = pledgesAmounts[i] / D64;
|
|
|
|
withdraw(idPledge, amount);
|
|
}
|
|
}
|
|
|
|
function mConfirmPayment(uint[] pledgesAmounts) {
|
|
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
|
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
|
uint amount = pledgesAmounts[i] / D64;
|
|
|
|
confirmPayment(idPledge, amount);
|
|
}
|
|
}
|
|
|
|
function mCancelPayment(uint[] pledgesAmounts) {
|
|
for (uint i = 0; i < pledgesAmounts.length; i++ ) {
|
|
uint64 idPledge = uint64( pledgesAmounts[i] & (D64-1) );
|
|
uint amount = pledgesAmounts[i] / D64;
|
|
|
|
cancelPayment(idPledge, amount);
|
|
}
|
|
}
|
|
|
|
function mNormalizePledge(uint[] pledges) returns(uint64) {
|
|
for (uint i = 0; i < pledges.length; i++ ) {
|
|
uint64 idPledge = uint64( pledges[i] & (D64-1) );
|
|
|
|
normalizePledge(idPledge);
|
|
}
|
|
}
|
|
|
|
////////
|
|
// Private methods
|
|
///////
|
|
|
|
// this function is obvious, but it can also be called to undelegate everyone
|
|
// by setting yourself as the idReceiver
|
|
function transferOwnershipToProject(uint64 idPledge, uint amount, uint64 idReceiver) internal {
|
|
Pledge storage n = findPledge(idPledge);
|
|
|
|
require(getPledgeLevel(n) < MAX_INTERPROJECT_LEVEL);
|
|
require(!isProjectCanceled(idReceiver));
|
|
|
|
uint64 oldPledge = findOrCreatePledge(
|
|
n.owner,
|
|
n.delegationChain,
|
|
0,
|
|
0,
|
|
n.oldPledge,
|
|
PaymentState.Pledged);
|
|
uint64 toPledge = findOrCreatePledge(
|
|
idReceiver,
|
|
new uint64[](0),
|
|
0,
|
|
0,
|
|
oldPledge,
|
|
PaymentState.Pledged);
|
|
doTransfer(idPledge, toPledge, amount);
|
|
}
|
|
|
|
function transferOwnershipToGiver(uint64 idPledge, uint amount, uint64 idReceiver) internal {
|
|
uint64 toPledge = findOrCreatePledge(
|
|
idReceiver,
|
|
new uint64[](0),
|
|
0,
|
|
0,
|
|
0,
|
|
PaymentState.Pledged);
|
|
doTransfer(idPledge, toPledge, amount);
|
|
}
|
|
|
|
function appendDelegate(uint64 idPledge, uint amount, uint64 idReceiver) internal {
|
|
Pledge storage n= findPledge(idPledge);
|
|
|
|
require(n.delegationChain.length < MAX_DELEGATES); //TODO change to revert and say the error
|
|
uint64[] memory newDelegationChain = new uint64[](n.delegationChain.length + 1);
|
|
for (uint i=0; i<n.delegationChain.length; i++) {
|
|
newDelegationChain[i] = n.delegationChain[i];
|
|
}
|
|
|
|
// Make the last item in the array the idReceiver
|
|
newDelegationChain[n.delegationChain.length] = idReceiver;
|
|
|
|
uint64 toPledge = findOrCreatePledge(
|
|
n.owner,
|
|
newDelegationChain,
|
|
0,
|
|
0,
|
|
n.oldPledge,
|
|
PaymentState.Pledged);
|
|
doTransfer(idPledge, toPledge, amount);
|
|
}
|
|
|
|
/// @param q Number of undelegations
|
|
function undelegate(uint64 idPledge, uint amount, uint q) internal returns (uint64){
|
|
Pledge storage n = findPledge(idPledge);
|
|
uint64[] memory newDelegationChain = new uint64[](n.delegationChain.length - q);
|
|
for (uint i=0; i<n.delegationChain.length - q; i++) {
|
|
newDelegationChain[i] = n.delegationChain[i];
|
|
}
|
|
uint64 toPledge = findOrCreatePledge(
|
|
n.owner,
|
|
newDelegationChain,
|
|
0,
|
|
0,
|
|
n.oldPledge,
|
|
PaymentState.Pledged);
|
|
doTransfer(idPledge, toPledge, amount);
|
|
|
|
return toPledge;
|
|
}
|
|
|
|
|
|
function proposeAssignProject(uint64 idPledge, uint amount, uint64 idReceiver) internal {// Todo rename
|
|
Pledge storage n = findPledge(idPledge);
|
|
|
|
require(getPledgeLevel(n) < MAX_SUBPROJECT_LEVEL);
|
|
require(!isProjectCanceled(idReceiver));
|
|
|
|
uint64 toPledge = findOrCreatePledge(
|
|
n.owner,
|
|
n.delegationChain,
|
|
idReceiver,
|
|
uint64(getTime() + maxCommitTime(n)),
|
|
n.oldPledge,
|
|
PaymentState.Pledged);
|
|
doTransfer(idPledge, toPledge, amount);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// This function does 2 things, #1: it checks to make sure that the pledges are correct
|
|
// if the a pledged project has already been committed then it changes the owner
|
|
// to be the proposed project (Pledge that 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: It checks to make sure that if there has been a cancellation in the chain of projects,
|
|
// then it adjusts the pledge's owner appropriately.
|
|
// This call can be called from any body at any time 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
|
|
function normalizePledge(uint64 idPledge) returns(uint64) {
|
|
Pledge storage n = findPledge(idPledge);
|
|
|
|
// Check to make sure this pledge hasnt already been used or is in the process of being used
|
|
if (n.paymentState != PaymentState.Pledged) return idPledge;
|
|
|
|
// First send to a project if it's proposed and commited
|
|
if ((n.intendedProject > 0) && ( getTime() > n.commitTime)) {
|
|
uint64 oldPledge = findOrCreatePledge(
|
|
n.owner,
|
|
n.delegationChain,
|
|
0,
|
|
0,
|
|
n.oldPledge,
|
|
PaymentState.Pledged);
|
|
uint64 toPledge = findOrCreatePledge(
|
|
n.intendedProject,
|
|
new uint64[](0),
|
|
0,
|
|
0,
|
|
oldPledge,
|
|
PaymentState.Pledged);
|
|
doTransfer(idPledge, toPledge, n.amount);
|
|
idPledge = toPledge;
|
|
n = findPledge(idPledge);
|
|
}
|
|
|
|
toPledge = getOldestPledgeNotCanceled(idPledge);// TODO toPledge is pledge defined
|
|
if (toPledge != idPledge) {
|
|
doTransfer(idPledge, toPledge, n.amount);
|
|
}
|
|
|
|
return toPledge;
|
|
}
|
|
|
|
/////////////
|
|
// Plugins
|
|
/////////////
|
|
|
|
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);
|
|
if ((address(admin.plugin) != 0) && (allowedAmount > 0)) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
function callPluginsPledge(bool before, uint64 idPledge, uint64 fromPledge, uint64 toPledge, uint amount) internal returns (uint allowedAmount) {
|
|
uint64 offset = idPledge == fromPledge ? 0 : 256;
|
|
allowedAmount = amount;
|
|
Pledge storage n = findPledge(idPledge);
|
|
|
|
allowedAmount = callPlugin(before, n.owner, fromPledge, toPledge, offset, allowedAmount);
|
|
|
|
for (uint64 i=0; i<n.delegationChain.length; i++) {
|
|
allowedAmount = callPlugin(before, n.delegationChain[i], fromPledge, toPledge, offset + i+1, allowedAmount);
|
|
}
|
|
|
|
if (n.intendedProject > 0) {
|
|
allowedAmount = callPlugin(before, n.intendedProject, fromPledge, toPledge, offset + 255, allowedAmount);
|
|
}
|
|
}
|
|
|
|
function callPlugins(bool before, uint64 fromPledge, uint64 toPledge, uint amount) internal returns (uint allowedAmount) {
|
|
allowedAmount = amount;
|
|
|
|
allowedAmount = callPluginsPledge(before, fromPledge, fromPledge, toPledge, allowedAmount);
|
|
allowedAmount = callPluginsPledge(before, toPledge, fromPledge, toPledge, allowedAmount);
|
|
}
|
|
|
|
/////////////
|
|
// Test functions
|
|
/////////////
|
|
|
|
function getTime() internal returns (uint) {
|
|
return now;
|
|
}
|
|
|
|
event Transfer(uint64 indexed from, uint64 indexed to, uint amount);
|
|
event CancelProject(uint64 indexed idProject);
|
|
|
|
}
|
|
|
|
//File: ./contracts/LiquidPledgingMock.sol
|
|
pragma solidity ^0.4.11;
|
|
|
|
|
|
|
|
// @dev LiquidPledgingMock mocks current block number
|
|
|
|
contract LiquidPledgingMock is LiquidPledging {
|
|
|
|
uint public mock_time;
|
|
|
|
function LiquidPledgingMock(address _vault) LiquidPledging(_vault) {
|
|
mock_time = now;
|
|
}
|
|
|
|
function getTime() internal returns (uint) {
|
|
return mock_time;
|
|
}
|
|
|
|
function setMockedTime(uint _t) {
|
|
mock_time = _t;
|
|
}
|
|
}
|