Fixes and some tests. Not ready yet

This commit is contained in:
Jordi Baylina 2017-06-26 19:54:28 +02:00
parent 3f6c3ad398
commit feda565745
13 changed files with 13122 additions and 18 deletions

49
.eslintrc Normal file
View File

@ -0,0 +1,49 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"mocha": true
},
"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
// indentation
"indent": [ 2, 4 ],
// spacing
"template-curly-spacing": [ 2, "always" ],
"array-bracket-spacing": [ 2, "always" ],
"object-curly-spacing": [ 2, "always" ],
"computed-property-spacing": [ 2, "always" ],
"no-multiple-empty-lines": [ 2, { "max": 1, "maxEOF": 0, "maxBOF": 0 } ],
// strings
"quotes": [ 2, "double", "avoid-escape" ],
// code arrangement matter
"no-use-before-define": [ 2, { "functions": false } ],
// make it meaningful
"prefer-const": 1,
// keep it simple
"complexity": [ 1, 5 ],
// Consisten return
"consistent-return": 0,
"import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js", "**/compile.js", "**/test/*.js"]}],
// react
"react/prefer-es6-class": 0,
"react/jsx-filename-extension": 0,
"react/jsx-indent": [ 2, 4 ]
},
"globals": {
"artifacts": true,
"web3": true,
"contract": true,
"assert": true
}
}

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/
build/
.DS_Store
.mypy_cache
__pycache__

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
dist: trusty
sudo: false
group: beta
language: node_js
node_js:
- "7"
before_install:
- npm install truffle@3.2.3 -g
- npm i -g ethereumjs-testrpc
script:
- testrpc -l 100000000 -i 15 > /dev/null &
- truffle test

View File

@ -24,7 +24,7 @@ contract ILiquidPledging {
); );
function numberOfManagers() constant returns(uint); function numberOfNoteManagers() constant returns(uint);
function getNoteManager(uint64 idManager) constant returns ( function getNoteManager(uint64 idManager) constant returns (
NoteManagerType managerType, NoteManagerType managerType,
@ -34,14 +34,20 @@ contract ILiquidPledging {
address reviewer, address reviewer,
bool canceled); bool canceled);
function addDonor(string name, uint commitTime); event DonorAdded(uint64 indexed idMember);
function updateDonor(uint64 idDonor, address newAddr, string newName, uint newCommitTime);
function addDonor(string name, uint64 commitTime);
function updateDonor(
uint64 idDonor,
address newAddr,
string newName,
uint64 newCommitTime);
function addDelegate(string name); function addDelegate(string name);
function updateDelegate(uint64 idDelegate, address newAddr, string newName); function updateDelegate(uint64 idDelegate, address newAddr, string newName);
function addProject(string name, address canceler, uint commitTime) ; function addProject(string name, address canceler, uint64 commitTime) ;
function updateProject(uint64 idProject, address newAddr, string newName, uint newCommitTime); function updateProject(uint64 idProject, address newAddr, string newName, uint64 newCommitTime);
function updateProjectCanceler(uint64 idProject, address newCanceler); function updateProjectCanceler(uint64 idProject, address newCanceler);
function donate(uint64 idDonor, uint64 idReceiver) payable; function donate(uint64 idDonor, uint64 idReceiver) payable;

View File

@ -32,11 +32,16 @@ contract LiquidPledging is LiquidPledgingBase {
PaymentState.NotPaid); PaymentState.NotPaid);
doTransfer(0, idNote, amount); Note nTo = findNote(idNote);
nTo.amount += amount;
Transfer(0, idNote, amount);
transfer(idDonor, idNote, amount, idReceiver); transfer(idDonor, idNote, amount, idReceiver);
} }
uint64 public test;
uint64 public test2;
function transfer(uint64 idSender, uint64 idNote, uint amount, uint64 idReceiver) { function transfer(uint64 idSender, uint64 idNote, uint amount, uint64 idReceiver) {
@ -123,7 +128,7 @@ contract LiquidPledging is LiquidPledgingBase {
n.delegationChain, n.delegationChain,
0, 0,
0, 0,
idNote, n.oldNote,
PaymentState.Paying PaymentState.Paying
); );
@ -144,7 +149,7 @@ contract LiquidPledging is LiquidPledgingBase {
n.delegationChain, n.delegationChain,
0, 0,
0, 0,
idNote, n.oldNote,
PaymentState.Paid PaymentState.Paid
); );
@ -162,7 +167,7 @@ contract LiquidPledging is LiquidPledgingBase {
n.delegationChain, n.delegationChain,
0, 0,
0, 0,
idNote, n.oldNote,
PaymentState.NotPaid PaymentState.NotPaid
); );
@ -234,6 +239,9 @@ contract LiquidPledging is LiquidPledgingBase {
0, 0,
n.oldNote, n.oldNote,
PaymentState.NotPaid); PaymentState.NotPaid);
// If the owner does not change, then just let it this way.
if (n.owner == idReceiver) return;
uint64 toNote = findNote( uint64 toNote = findNote(
idReceiver, idReceiver,
new uint64[](0), new uint64[](0),
@ -286,13 +294,15 @@ contract LiquidPledging is LiquidPledgingBase {
n.owner, n.owner,
n.delegationChain, n.delegationChain,
idReceiver, idReceiver,
uint64(now + owner.commitTime), uint64(getTime() + owner.commitTime),
n.oldNote, n.oldNote,
PaymentState.NotPaid); PaymentState.NotPaid);
doTransfer(idNote, toNote, amount); doTransfer(idNote, toNote, amount);
} }
function doTransfer(uint64 from, uint64 to, uint amount) internal { function doTransfer(uint64 from, uint64 to, uint amount) internal {
if (from == to) return;
if (amount == 0) return;
Note nFrom = findNote(from); Note nFrom = findNote(from);
Note nTo = findNote(to); Note nTo = findNote(to);
if (nFrom.amount < amount) throw; if (nFrom.amount < amount) throw;
@ -307,7 +317,7 @@ contract LiquidPledging is LiquidPledgingBase {
if (n.paymentState != PaymentState.NotPaid) return idNote; if (n.paymentState != PaymentState.NotPaid) return idNote;
// First send to a project if it's proposed and commited // First send to a project if it's proposed and commited
if ((n.proposedProject > 0) && ( now > n.commmitTime)) { if ((n.proposedProject > 0) && ( getTime() > n.commmitTime)) {
uint64 oldNote = findNote( uint64 oldNote = findNote(
n.owner, n.owner,
n.delegationChain, n.delegationChain,
@ -323,12 +333,23 @@ contract LiquidPledging is LiquidPledgingBase {
oldNote, oldNote,
PaymentState.NotPaid); PaymentState.NotPaid);
doTransfer(idNote, toNote, n.amount); doTransfer(idNote, toNote, n.amount);
idNote = toNote;
} }
toNote = getOldestNoteNotCanceled(idNote); toNote = getOldestNoteNotCanceled(idNote);
if (toNote != idNote) { if (toNote != idNote) {
doTransfer(idNote, toNote, n.amount); doTransfer(idNote, toNote, n.amount);
} }
return toNote;
}
/////////////
// Test functions
/////////////
function getTime() internal returns (uint) {
return now;
} }
event Transfer(uint64 indexed from, uint64 indexed to, uint amount); event Transfer(uint64 indexed from, uint64 indexed to, uint amount);

View File

@ -1,12 +1,11 @@
pragma solidity ^0.4.11; pragma solidity ^0.4.11;
import "./ILiquidPledging.sol"; import "./Vault.sol";
contract Vault { contract LiquidPledgingBase {
function authorizePayment(bytes32 ref, address dest, uint amount);
}
contract LiquidPledgingBase is ILiquidPledging { enum NoteManagerType { Donor, Delegate, Project }
enum PaymentState {NotPaid, Paying, Paid}
struct NoteManager { struct NoteManager {
NoteManagerType managerType; NoteManagerType managerType;
@ -185,13 +184,13 @@ contract LiquidPledgingBase is ILiquidPledging {
string name string name
) { ) {
Note n = findNote(idNote); Note n = findNote(idNote);
idDelegate = n.delegationChain[idxDelegate]; idDelegate = n.delegationChain[idxDelegate - 1];
NoteManager delegate = findManager(idDelegate); NoteManager delegate = findManager(idDelegate);
addr = delegate.addr; addr = delegate.addr;
name = delegate.name; name = delegate.name;
} }
function numberOfManagers() constant returns(uint) { function numberOfNoteManagers() constant returns(uint) {
return managers.length - 1; return managers.length - 1;
} }
@ -229,6 +228,7 @@ contract LiquidPledgingBase is ILiquidPledging {
uint64 idx = hNote2ddx[hNote]; uint64 idx = hNote2ddx[hNote];
if (idx > 0) return idx; if (idx > 0) return idx;
idx = uint64(notes.length); idx = uint64(notes.length);
hNote2ddx[hNote] = idx;
notes.push(Note(0, owner, delegationChain, proposedProject, commmitTime, oldNote, paid)); notes.push(Note(0, owner, delegationChain, proposedProject, commmitTime, oldNote, paid));
return idx; return idx;
} }

37
contracts/Owned.sol Normal file
View File

@ -0,0 +1,37 @@
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 message sender to be `owner`
function Owned() {
owner = msg.sender;
}
address public newOwner;
/// @notice `owner` can step down and assign some other address to this role
/// @param _newOwner The address of the new owner. 0x0 can be used to create
/// an unowned neutral vault, however that cannot be undone
function changeOwner(address _newOwner) onlyOwner {
newOwner = _newOwner;
}
function acceptOwnership() {
if (msg.sender == newOwner) {
owner = newOwner;
}
}
}

117
contracts/Vault.sol Normal file
View File

@ -0,0 +1,117 @@
pragma solidity ^0.4.11;
import "./Owned.sol";
import "./LiquidPledging.sol";
contract Vault is Owned {
LiquidPledging public liquidPledging;
bool public autoPay;
enum PaymentState {
Pending,
Paid,
Canceled
}
struct Payment {
PaymentState state;
bytes32 ref;
address dest;
uint amount;
}
Payment[] public payments;
modifier onlyLiquidPledging() {
require(msg.sender == address(liquidPledging));
_;
}
function VaultMock() {
}
function () payable {
}
function setLiquidPledging(address _newLiquidPledging) onlyOwner {
liquidPledging = LiquidPledging(_newLiquidPledging);
}
function setAutopay(bool _automatic) onlyOwner {
autoPay = _automatic;
}
function authorizePayment(bytes32 _ref, address _dest, uint _amount) onlyLiquidPledging returns (uint) {
uint idPayment = payments.length;
payments.length ++;
payments[idPayment].state = PaymentState.Pending;
payments[idPayment].ref = _ref;
payments[idPayment].dest = _dest;
payments[idPayment].amount = _amount;
AuthorizePayment(idPayment, _ref, _dest, _amount);
if (autoPay) doConfirmPayment(idPayment);
return idPayment;
}
function confirmPayment(uint _idPayment) onlyOwner {
doConfirmPayment(_idPayment);
}
function doConfirmPayment(uint _idPayment) internal {
require(_idPayment < payments.length);
Payment p = payments[_idPayment];
require(p.state == PaymentState.Pending);
p.state = PaymentState.Paid;
p.dest.transfer(p.amount);
liquidPledging.confirmPayment(uint64(p.ref), p.amount);
ConfirmPayment(_idPayment);
}
function cancelPayment(uint _idPayment) onlyOwner {
doCancelPayment(_idPayment);
}
function doCancelPayment(uint _idPayment) internal {
require(_idPayment < payments.length);
Payment p = payments[_idPayment];
require(p.state == PaymentState.Pending);
p.state = PaymentState.Canceled;
liquidPledging.cancelPayment(uint64(p.ref), p.amount);
CancelPayment(_idPayment);
}
function multiConfirm(uint[] _idPayments) onlyOwner {
for (uint i=0; i < _idPayments.length; i++) {
doConfirmPayment(_idPayments[i]);
}
}
function multiCancel(uint[] _idPayments) onlyOwner {
for (uint i=0; i < _idPayments.length; i++) {
doCancelPayment(_idPayments[i]);
}
}
function nPayments() constant returns (uint) {
return payments.length;
}
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

@ -0,0 +1,22 @@
pragma solidity ^0.4.11;
import '../LiquidPledging.sol';
// @dev DevTokensHolderMock mocks current block number
contract LiquidPledgingMock is LiquidPledging {
uint 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;
}
}

12562
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "liquidpledging",
"version": "0.0.1",
"description": "Liquid Pledging Smart Contract",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "truffle test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Giveth/liquidpledging.git"
},
"keywords": [
"liquid",
"pledging",
"tracking",
"smart",
"contract",
"solidity",
"donation"
],
"author": "Jordi Baylina",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/Giveth/liquidpledging/issues"
},
"devDependencies": {
"async": "^2.4.0",
"bignumber.js": "^4.0.2",
"ethereumjs-testrpc": "^3.0.5",
"random-bytes": "^1.0.0",
"truffle": "3.2.4",
"truffle-hdwallet-provider": "0.0.3",
"web3": "^0.18.4"
},
"homepage": "https://github.com/Giveth/liquidpledging#readme",
"dependencies": {
"babel-eslint": "^7.2.3"
}
}

184
test/NormalOperation.js Normal file
View File

@ -0,0 +1,184 @@
const LiquidPledging = artifacts.require("LiquidPledgingMock");
const Vault = artifacts.require("Vault");
contract("LiquidPledging", (accounts) => {
let liquidPledging;
let vault;
let donor1 = accounts[0];
let delegate1 = accounts[1];
let adminProject1 = accounts[2];
let adminProject2 = accounts[3];
let reviewer = accounts[4];
it("Should deploy LiquidPledgin contract", async () => {
vault = await Vault.new();
liquidPledging = await LiquidPledging.new(vault.address);
await vault.setLiquidPledging(liquidPledging.address);
});
it("Should create a donor", async () => {
await liquidPledging.addDonor("Donor1", 86400, {from: donor1});
const nManagers = await liquidPledging.numberOfNoteManagers();
assert.equal(nManagers, 1);
const res = await liquidPledging.getNoteManager(1);
assert.equal(res[0], 0); // Donor
assert.equal(res[1], donor1);
assert.equal(res[2], "Donor1");
assert.equal(res[3], 86400);
});
it("Should make a donation", async () => {
await liquidPledging.donate(1, 1, {from: donor1, value: web3.toWei(1)});
const nNotes = await liquidPledging.numberOfNotes();
assert.equal(nNotes.toNumber(), 1);
const res = await liquidPledging.getNote(1);
});
it("Should create a delegate", async () => {
await liquidPledging.addDelegate("Delegate1", {from: delegate1});
const nManagers = await liquidPledging.numberOfNoteManagers();
assert.equal(nManagers, 2);
const res = await liquidPledging.getNoteManager(2);
assert.equal(res[0], 1); // Donor
assert.equal(res[1], delegate1);
assert.equal(res[2], "Delegate1");
});
it("Donor should delegate on the delegate ", async () => {
await liquidPledging.transfer(1, 1, web3.toWei(0.5), 2);
const nNotes = await liquidPledging.numberOfNotes();
assert.equal(nNotes.toNumber(), 2);
const res1 = await liquidPledging.getNote(1);
assert.equal(res1[0].toNumber(), web3.toWei(0.5));
const res2 = await liquidPledging.getNote(2);
assert.equal(res2[0].toNumber(), web3.toWei(0.5));
assert.equal(res2[1].toNumber(), 1); // One delegate
const d = await liquidPledging.getNoteDelegate(2, 1);
assert.equal(d[0], 2);
assert.equal(d[1], delegate1);
assert.equal(d[2], "Delegate1");
});
it("Should create a 2 projects", async () => {
await liquidPledging.addProject("Project1", reviewer, 86400, {from: adminProject1});
const nManagers = await liquidPledging.numberOfNoteManagers();
assert.equal(nManagers, 3);
const res = await liquidPledging.getNoteManager(3);
assert.equal(res[0], 2); // Project type
assert.equal(res[1], adminProject1);
assert.equal(res[2], "Project1");
assert.equal(res[3], 86400);
assert.equal(res[4], reviewer);
assert.equal(res[5], false);
await liquidPledging.addProject("Project2", reviewer, 86400, {from: adminProject2});
const nManagers2 = await liquidPledging.numberOfNoteManagers();
assert.equal(nManagers2, 4);
const res4 = await liquidPledging.getNoteManager(4);
assert.equal(res4[0], 2); // Project type
assert.equal(res4[1], adminProject2);
assert.equal(res4[2], "Project2");
assert.equal(res4[3], 86400);
assert.equal(res4[4], reviewer);
assert.equal(res4[5], false);
});
it("Delegate should assign to project1", async () => {
const n = Math.floor(new Date().getTime() / 1000);
await liquidPledging.transfer(2, 2, web3.toWei(0.2), 3, {from: delegate1});
const nNotes = await liquidPledging.numberOfNotes();
assert.equal(nNotes.toNumber(), 3);
const res3 = await liquidPledging.getNote(3);
assert.equal(res3[0].toNumber(), web3.toWei(0.2));
assert.equal(res3[1].toNumber(), 1); // Owner
assert.equal(res3[2].toNumber(), 1); // Delegates
assert.equal(res3[3].toNumber(), 3); // Proposed Project
assert.isAbove(res3[4], n + 86000);
assert.equal(res3[5].toNumber(), 0); // Old Node
assert.equal(res3[6].toNumber(), 0); // Not Paid
});
it("Donor should change his mind and assign half of it to project2", async () => {
const n = Math.floor(new Date().getTime() / 1000);
await liquidPledging.transfer(1, 3, web3.toWei(0.1), 4, {from: donor1});
const nNotes = await liquidPledging.numberOfNotes();
assert.equal(nNotes.toNumber(), 4);
const res3 = await liquidPledging.getNote(3);
assert.equal(res3[0].toNumber(), web3.toWei(0.1));
const res4 = await liquidPledging.getNote(4);
assert.equal(res4[1].toNumber(), 4); // Owner
assert.equal(res4[2].toNumber(), 0); // Delegates
assert.equal(res4[3].toNumber(), 0); // Proposed Project
assert.equal(res4[4], 0);
assert.equal(res4[5].toNumber(), 2); // Old Node
assert.equal(res4[6].toNumber(), 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.withdraw(3, web3.toWei(0.05), {from: adminProject1});
const nNotes = await liquidPledging.numberOfNotes();
assert.equal(nNotes.toNumber(), 6);
const res5 = await liquidPledging.getNote(5);
assert.equal(res5[0].toNumber(), web3.toWei(0.05));
assert.equal(res5[1].toNumber(), 3); // Owner
assert.equal(res5[2].toNumber(), 0); // Delegates
assert.equal(res5[3].toNumber(), 0); // Proposed Project
assert.equal(res5[4], 0); // commit time
assert.equal(res5[5].toNumber(), 2); // Old Node
assert.equal(res5[6].toNumber(), 0); // Not Paid
const res6 = await liquidPledging.getNote(6);
assert.equal(res6[0].toNumber(), web3.toWei(0.05));
assert.equal(res6[1].toNumber(), 3); // Owner
assert.equal(res6[2].toNumber(), 0); // Delegates
assert.equal(res6[3].toNumber(), 0); // Proposed Project
assert.equal(res6[4], 0); // commit time
assert.equal(res6[5].toNumber(), 2); // Old Node
assert.equal(res6[6].toNumber(), 1); // Peinding paid Paid
});
it("Should collect the Ether", async () => {
const initialBalance = await web3.eth.getBalance(adminProject1);
await vault.confirmPayment(0);
const finalBalance = await web3.eth.getBalance(adminProject1);
const collected = web3.fromWei(finalBalance.sub(initialBalance)).toNumber();
assert.equal(collected, 0.05);
const nNotes = await liquidPledging.numberOfNotes();
assert.equal(nNotes.toNumber(), 7);
const res7 = await liquidPledging.getNote(7);
assert.equal(res7[0].toNumber(), web3.toWei(0.05));
assert.equal(res7[1].toNumber(), 3); // Owner
assert.equal(res7[2].toNumber(), 0); // Delegates
assert.equal(res7[3].toNumber(), 0); // Proposed Project
assert.equal(res7[4], 0); // commit time
assert.equal(res7[5].toNumber(), 2); // Old Node
assert.equal(res7[6].toNumber(), 2); // Peinding paid Paid
});
it("Reviewer should be able to cancel project1", async () => {
});
it("Delegate should send part of this ETH to project2", async () => {
});
it("Owner should be able to send the remaining to project2", async () => {
});
it("A subproject 2a and a delegate2 is created", async () => {
});
it("Project 2 delegate in delegate2", async () => {
});
it("delegate2 assigns to projec2a", async () => {
});
it("project2a spends on a while", async () => {
});
it("project2 is canceled", async () => {
});
it("original owner should recover the remaining funds", async () => {
});
});

45
truffle.js Normal file
View File

@ -0,0 +1,45 @@
const HDWalletProvider = require('truffle-hdwallet-provider');
const mnemonic = process.env.TEST_MNEMONIC || 'giveth liquid pledging mnemonic';
const providerRopsten = new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/', 0);
const providerKovan = new HDWalletProvider(mnemonic, 'https://kovan.infura.io', 0);
module.exports = {
networks: {
development: {
network_id: 15,
host: "localhost",
port: 8545,
gas: 4000000,
gasPrice: 20e9,
},
development_migrate: {
network_id: 15,
host: "localhost",
port: 8545,
gas: 4000000,
gasPrice: 20e9,
from: "0xf93df8c288b9020e76583a6997362e89e0599e99",
},
mainnet: {
network_id: 1,
host: "localhost",
port: 8545,
gas: 4000000,
gasPrice: 20e9,
from: "0xf93df8c288b9020e76583a6997362e89e0599e99",
},
ropsten: {
network_id: 3,
provider: providerRopsten,
gas: 4000000,
gasPrice: 20e9,
},
kovan: {
network_id: 42,
provider: providerKovan,
gas: 4000000,
gasPrice: 20e9,
},
}
};