add tests

This commit is contained in:
Ricardo Guilherme Schmidt 2019-03-20 22:01:06 -03:00
parent e2199110c8
commit 6bc051e211
No known key found for this signature in database
GPG Key ID: BFB3F5C8ED618A94
4 changed files with 718 additions and 23 deletions

View File

@ -21,6 +21,27 @@ config({
}
});
/**
* Delegations: Root, Sticker Market, ENS Usernames, Version Listings
* Functions:
* - Root
* - Sticker Market
* - change controller : Absolute Majority
* - change rates : Simple Majority
* - purgconte pack : Simple Majority
* - ENS Usernames
* - change controller : Simple Majority
* - migrate registry :
* - change price
* - Version Listing
* - change developer address
* - remove version
* - add release category (android,ios,linux,etc)
*
*
*
*/
contract("DelegationBase", function() {
this.timeout(0);
var defaultDelegate;
@ -129,6 +150,7 @@ contract("DelegationBase", function() {
})
it("Child a2 delegate to a4", async function () {
result = await ChildDelegation.methods.delegate(accounts[4]).send({from: accounts[2]})
const delegateArgs = result.events.Delegate.returnValues;
assert.equal(delegateArgs.who, accounts[2])
@ -137,6 +159,7 @@ contract("DelegationBase", function() {
result = await ChildDelegation.methods.delegatedTo(accounts[2]).call()
assert.equal(result, accounts[4])
result = await ChildDelegation.methods.delegationOf(accounts[0]).call()
assert.equal(result, defaultDelegate)
result = await ChildDelegation.methods.delegationOf(accounts[1]).call()
@ -144,7 +167,27 @@ contract("DelegationBase", function() {
result = await ChildDelegation.methods.delegationOf(accounts[2]).call()
assert.equal(result, defaultDelegate)
 
})
it("default delegate should be able to delegate", async function () {
await ChildDelegation.methods.delegate(accounts[6]).send({from: accounts[6]})
newDelegate = await ChildDelegation.methods.delegationOf(accounts[6]).call()
result = await ChildDelegation.methods.delegate(accounts[6]).send({from: defaultDelegate})
const delegateArgs = result.events.Delegate.returnValues;
assert.equal(delegateArgs.who, defaultDelegate)
assert.equal(delegateArgs.to, accounts[6])
result = await ChildDelegation.methods.delegatedTo(defaultDelegate).call()
assert.equal(result, accounts[6])
result = await ChildDelegation.methods.delegationOf(accounts[0]).call()
assert.equal(result, newDelegate)
result = await ChildDelegation.methods.delegationOf(accounts[1]).call()
assert.equal(result, newDelegate)
result = await ChildDelegation.methods.delegationOf(accounts[2]).call()
assert.equal(result, newDelegate)
})
})

378
test/proposal.js Normal file
View File

@ -0,0 +1,378 @@
const utils = require("../utils/testUtils")
const DefaultDelegation = require('Embark/contracts/DefaultDelegation');
const DelegationFactory = require('Embark/contracts/DelegationFactory');
const MiniMeToken = require('Embark/contracts/MiniMeToken');
const Delegation = require('Embark/contracts/Delegation');
const ProposalFactory = require('Embark/contracts/ProposalFactory');
const Proposal = require('Embark/contracts/Proposal');
const ProposalBase = require('Embark/contracts/ProposalBase');
config({
contracts: {
"MiniMeTokenFactory": {
},
"MiniMeToken": {
"args": [
"$MiniMeTokenFactory",
utils.zeroAddress,
0,
"TestMiniMeToken",
18,
"TST",
true
]
},
"DefaultDelegation": {
"args": [ "$accounts[5]" ]
},
"DelegationBase": {
"args": [ utils.zeroAddress ]
},
"DelegationInit": {},
"DelegationFactory": {
"args": ["$DelegationBase", "$DelegationInit", utils.zeroAddress]
},
"ProposalBase": {},
"ProposalInit": {},
"ProposalFactory": {
"args": ["$ProposalBase", "$ProposalInit", utils.zeroAddress]
}
}
});
function mintTokens(accounts, amount) {
return Promise.all(
accounts.map((account) => {
return MiniMeToken.methods.generateTokens(account, amount).send({from: accounts[0]});
})
);
}
function newDelegation(topDelegation) {
return new Promise((resolve, reject) => {
DelegationFactory.methods.createDelegation(topDelegation).send().on('receipt', (receipt) => {
resolve(new web3.eth.Contract(Delegation._jsonInterface, receipt.events.InstanceCreated.returnValues[0]));
}).on('error', (error) => {
reject(error);
});
});
}
async function addGas(call, from, amount=21000) {
return call.send({
gas: await call.estimateGas({gas: 8000000})+amount,
from: from
});
}
async function tabulateDirect(proposal, account) {
return addGas(proposal.methods.tabulateDirect(account), web3.eth.defaultAccount);
}
async function tabulateDelegated(proposal, account) {
return addGas(proposal.methods.tabulateDelegated(account), web3.eth.defaultAccount);
}
async function tabulateSigned(proposal, sig) {
return addGas(
proposal.methods.tabulateSigned(
sig.vote,
sig.position,
sig.proof,
sig.signature
),
web3.eth.defaultAccount
);
}
contract("Proposal", function() {
this.timeout(0);
const initialBalance = 17 * 10 ^ 18;
var defaultDelegate;
var accounts;
var RootDelegation;
var ChildDelegation;
before(function(done) {
defaultDelegate = DefaultDelegation._address;
web3.eth.getAccounts().then((res) => {
web3.eth.defaultAccount = res[0]
accounts = res;
//let contactList = require("./contacts.json")
//contacts = Object.keys(contactList).map((key) => { return { name: contactList[key].name.en, address: utils.pubKeyToAddress(key), pubKey: key }})
//res = accounts.concat(contacts.map((obj) => {return obj.address } ));
mintTokens(res, initialBalance).then((mintReceipts) => {
newDelegation(defaultDelegate).then((createdRoot) => {
RootDelegation = createdRoot;
newDelegation(RootDelegation._address).then((createdChild) => {
ChildDelegation = createdChild;
Promise.all([
// root: 0 -> 1 -> 2 -> 3 (-> 5)
RootDelegation.methods.delegate(accounts[1]).send({from: accounts[0]}),
RootDelegation.methods.delegate(accounts[2]).send({from: accounts[1]}),
RootDelegation.methods.delegate(accounts[3]).send({from: accounts[2]}),
// root: 4 -> 4
RootDelegation.methods.delegate(accounts[4]).send({from: accounts[4]}),
// root: 6 -> 7 -> 8 -> 9 -> 6 (circular)
RootDelegation.methods.delegate(accounts[7]).send({from: accounts[6]}),
RootDelegation.methods.delegate(accounts[8]).send({from: accounts[7]}),
RootDelegation.methods.delegate(accounts[9]).send({from: accounts[8]}),
RootDelegation.methods.delegate(accounts[6]).send({from: accounts[9]}),
// child: 5 -> 6
ChildDelegation.methods.delegate(accounts[7]).send({from: accounts[5]})
]).then((delegateReceipts) => {
done();
})
})
})
});
});
});
const QUORUM_QUALIFIED = 0;
const QUORUM_MAJORITY = 1;
const QUORUM_SIMPLE = 2;
const VOTE_NULL = 0;
const VOTE_REJECT = 1;
const VOTE_APPROVE = 2;
var testProposal;
var blockStart
it("create proposal by factory", async function () {
let tabulationBlockDelay = 50;
blockStart = await web3.eth.getBlockNumber() + 10;
let blockEndDelay = 10;
receipt = await ProposalFactory.methods.createProposal(
MiniMeToken._address,
RootDelegation._address,
"0xDA0",
tabulationBlockDelay,
blockStart,
blockEndDelay,
QUORUM_QUALIFIED
).send()
testProposal = new web3.eth.Contract(ProposalBase._jsonInterface, receipt.events.InstanceCreated.returnValues[0]);
});
it("rejects signed and direct votes while not voting period ", async function () {
assert(await web3.eth.getBlockNumber() < blockStart, "Wrong block number")
assert(await utils.assertEVMException(testProposal.methods.voteDirect(VOTE_APPROVE).send({from: accounts[0]}), "Voting not started"), "Didnt rejected")
assert(await utils.assertEVMException(testProposal.methods.voteSigned(utils.zeroBytes32).send({from: accounts[0]}), "Voting not started"), "Didnt rejected")
});
it("increases block number to vote block start", async function () {
await utils.setBlockNumber(blockStart);
assert(await web3.eth.getBlockNumber() >= blockStart, "Wrong block number")
});
it("direct vote", async function () {
let receipt = await testProposal.methods.voteDirect(VOTE_APPROVE).send({from: accounts[5]});
assert.equal(receipt.events.Voted.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Voted.returnValues[1], accounts[5]);
});
const { MerkleTree } = require('../utils/merkleTree.js');
var sigs = [];
it("signed vote at voting period", async function () {
let vote = VOTE_APPROVE;
let approveHash = await testProposal.methods.getVoteHash(vote).call();
let signatures = await Promise.all(accounts.map((address) => {
//in web app should web3.eth.personal.sign(approveHash, address)
return web3.eth.sign(approveHash, address);
}))
let merkleTree = new MerkleTree(signatures);
let merkleRoot = merkleTree.getHexRoot();
let receipt = await testProposal.methods.voteSigned(merkleRoot).send({from: accounts[0]})
let position = 0;
assert.equal(receipt.events.VoteSignatures.returnValues[0], position)
assert.equal(receipt.events.VoteSignatures.returnValues[1], merkleRoot);
for(var i = 0; i < signatures.length; i++) {
sigs.push ({
position: position,
vote: vote,
signature: signatures[i],
proof: merkleTree.getHexProof(signatures[i]),
signer: accounts[i]
})
}
});
var voteBlockEnd;
it("tests under block end", async function () {
voteBlockEnd = await testProposal.methods.voteBlockEnd().call();
assert(await web3.eth.getBlockNumber() <= voteBlockEnd, "Current block number is too low for testing");
})
it("reject tabulateDirect when voting not ended", async function () {
assert(await utils.assertEVMException(tabulateDirect(testProposal, accounts[5]), "Block end not reached"), "Didnt rejected")
});
it("reject tabulateDelegated when voting not ended", async function () {
assert(await utils.assertEVMException(tabulateDelegated(testProposal, accounts[0]), "Block end not reached"), "Didnt rejected")
});
it("reject tabulateSigned when voting not ended", async function () {
assert(await utils.assertEVMException(tabulateSigned(testProposal, sigs[0]), "Block end not reached"), "Didnt rejected")
});
it("increases block number to vote block end", async function () {
await utils.setBlockNumber(voteBlockEnd+1);
assert(await web3.eth.getBlockNumber() > voteBlockEnd, "Wrong block number")
});
it("rejects direct votes when voting period ended", async function () {
assert(await utils.assertEVMException(testProposal.methods.voteDirect(VOTE_APPROVE).send({from: accounts[1]}), "Voting ended"), "Didnt rejected")
});
it("rejects signed votes when voting period ended", async function () {
assert(await utils.assertEVMException(testProposal.methods.voteSigned(utils.zeroBytes32).send({from: accounts[0]}), "Voting ended"), "Didnt rejected")
});
it("reject tabulates when no delegate voted", async function () {;
assert(await utils.assertEVMException(tabulateDelegated(testProposal, accounts[4]), "No delegate vote found"), "Didnt rejected 2")
});
it("tabulates influence from self voter", async function () {
let receipt = await tabulateDirect(testProposal, accounts[5]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[5]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[5]);
});
it("tabulates influence from default delegate", async function () {
receipt = await tabulateDelegated(testProposal, accounts[3]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[5]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[3]);
});
it("tabulates influence from voteSigned ", async function () {
let sig = sigs[2];
let receipt = await tabulateSigned(testProposal, sig);
assert.equal(receipt.events.Voted.returnValues[0], sig.vote, "emit Voted wrong Vote")
assert.equal(receipt.events.Voted.returnValues[1], sig.signer, "emit Voted wrong address");
assert.equal(receipt.events.Claimed.returnValues[0], sig.vote, "emit Claimed wrong Vote")
assert.equal(receipt.events.Claimed.returnValues[1], sig.signer, "emit Claimed wrong claimer");
assert.equal(receipt.events.Claimed.returnValues[2], sig.signer, "emit Claimed wrong source");
});
it("should not tabulate for delegate if voted ", async function () {
assert(await utils.assertEVMException(tabulateDelegated(testProposal, accounts[2]), "Not delegatable"), "Didnt rejected ")
assert(await utils.assertEVMException(tabulateDelegated(testProposal, accounts[5]), "Not delegatable"), "Didnt rejected 2")
});
it("tabulates influence from direct delegate ", async function () {
let receipt = await tabulateDelegated(testProposal, accounts[1]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[2]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[1])
});
it("tabulates influence from indirect delegate", async function () {
let receipt = await tabulateDelegated(testProposal, accounts[0]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[2]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[0])
});
it("should not tabulate influence from circular delegation chain when none voted", async function () {
assert(await utils.assertEVMException(tabulateDelegated(testProposal, accounts[7]), "revert"), "Didnt rejected ")
});
it("tabulates signature from circular delegation", async function () {
await tabulateSigned(testProposal, sigs[7]);
})
it("tabulates influence from circular direct delegate", async function () {
var receipt = await tabulateDelegated(testProposal, accounts[6]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[7]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[6])
})
it("tabulates influence from circular indirect delegate", async function () {
receipt = await tabulateDelegated(testProposal, accounts[9]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[7]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[9])
})
it("tabulates influence from circular delegate of claiming delegate", async function () {
receipt = await tabulateDelegated(testProposal, accounts[8]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[7]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[8])
})
it("retabulates influence to self vote", async function () {
sig = sigs[6];
receipt = await tabulateSigned(testProposal, sig);
assert.equal(receipt.events.Voted.returnValues[0], sig.vote, "emit Voted wrong Vote")
assert.equal(receipt.events.Voted.returnValues[1], sig.signer, "emit Voted wrong address");
assert.equal(receipt.events.Claimed.returnValues[0], sig.vote, "emit Claimed wrong Vote")
assert.equal(receipt.events.Claimed.returnValues[1], sig.signer, "emit Claimed wrong claimer");
assert.equal(receipt.events.Claimed.returnValues[2], sig.signer, "emit Claimed wrong source");
});
it("retabulates influence to indirect delegate", async function () {
receipt = await tabulateDelegated(testProposal, accounts[8]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[6]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[8])
});
it("retabulates influence to direct delegate", async function () {
receipt = await tabulateDelegated(testProposal, accounts[9]);
assert.equal(receipt.events.Claimed.returnValues[0], VOTE_APPROVE)
assert.equal(receipt.events.Claimed.returnValues[1], accounts[6]);
assert.equal(receipt.events.Claimed.returnValues[2], accounts[9])
});
it("create proposal by factory", async function () {
let tabulationBlockDelay = 50;
blockStart = await web3.eth.getBlockNumber();
let blockEndDelay = 10;
receipt = await ProposalFactory.methods.createProposal(
MiniMeToken._address,
ChildDelegation._address,
"0xDA0",
tabulationBlockDelay,
blockStart,
blockEndDelay,
QUORUM_QUALIFIED
).send()
testProposal = new web3.eth.Contract(ProposalBase._jsonInterface, receipt.events.InstanceCreated.returnValues[0]);
});
it("child delegation default parent chain break", async function () {
});
it("child delegation indirect parent chain break", async function () {
});
it("child delegation parent chain full break", async function () {
});
})

133
utils/merkleTree.js Normal file
View File

@ -0,0 +1,133 @@
const { sha3, bufferToHex } = require('ethereumjs-util');
class MerkleTree {
constructor(elements) {
// Filter empty strings and hash elements
this.elements = elements.filter(el => el).map(el => sha3(el));
// Deduplicate elements
this.elements = this.bufDedup(this.elements);
// Sort elements
this.elements.sort(Buffer.compare);
// Create layers
this.layers = this.getLayers(this.elements);
}
getLayers(elements) {
if (elements.length == 0) {
return [[""]];
}
const layers = [];
layers.push(elements);
// Get next layer until we reach the root
while (layers[layers.length - 1].length > 1) {
layers.push(this.getNextLayer(layers[layers.length - 1]));
}
return layers;
}
getNextLayer(elements) {
return elements.reduce((layer, el, idx, arr) => {
if (idx % 2 === 0) {
// Hash the current element with its pair element
layer.push(this.combinedHash(el, arr[idx + 1]));
}
return layer;
}, []);
}
combinedHash(first, second) {
if (!first) { return second; }
if (!second) { return first; }
return sha3(this.sortAndConcat(first, second));
}
getRoot() {
return this.layers[this.layers.length - 1][0];
}
getHexRoot() {
return bufferToHex(this.getRoot());
}
getProof(el) {
let idx = this.bufIndexOf(el, this.elements);
if (idx === -1) {
throw new Error("Element does not exist in Merkle tree");
}
return this.layers.reduce((proof, layer) => {
const pairElement = this.getPairElement(idx, layer);
if (pairElement) {
proof.push(pairElement);
}
idx = Math.floor(idx / 2);
return proof;
}, []);
}
getHexProof(el) {
const proof = this.getProof(el);
return this.bufArrToHex(proof);
}
getPairElement(idx, layer) {
const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
if (pairIdx < layer.length) {
return layer[pairIdx];
} else {
return null;
}
}
bufIndexOf(el, arr) {
let hash;
// Convert element to 32 byte hash if it is not one already
if (el.length !== 32 || !Buffer.isBuffer(el)) {
hash = sha3(el);
} else {
hash = el;
}
for (let i = 0; i < arr.length; i++) {
if (hash.equals(arr[i])) {
return i;
}
}
return -1;
}
bufDedup(elements) {
return elements.filter((el, idx) => {
return this.bufIndexOf(el, elements) === idx;
});
}
bufArrToHex(arr) {
if (arr.some(el => !Buffer.isBuffer(el))) {
throw new Error("Array is not an array of buffers");
}
return arr.map(el => '0x' + el.toString('hex'));
}
sortAndConcat(...args) {
return Buffer.concat([...args].sort(Buffer.compare));
}
}
exports.MerkleTree = MerkleTree;

View File

@ -43,6 +43,13 @@ exports.addressToBytes32 = (address) => {
return "0x" + stringed.substring(stringed.length - 64, stringed.length);
}
exports.pubKeyToAddress = (contactCode) => {
if(contactCode.length != 132 || contactCode.substring(0,4) != "0x04") {
throw "Invalid contact code: " +contactCode;
}
return web3.utils.toChecksumAddress("0x" + web3.utils.soliditySha3({t: 'bytes', v: ("0x" + contactCode.substring(4))}).substring(26));
}
// OpenZeppelin's expectThrow helper -
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
@ -200,6 +207,73 @@ exports.ensureException = function(error) {
assert(isException(error), error.toString());
};
const NO_ERROR = 0;
const FATAL_ERROR = 1;
const OUT_OF_GAS = 2;
const USER_ERROR = 3;
exports.getEVMException = async function(sendFn) {
var error = null;
try {
await sendFn.estimateGas();
} catch(e) {
var a = e.toString().split(": ");
if(a[0] != "RuntimeError" || a[1] != "VM Exception while processing transaction") {
throw e;
}
switch (a[3]){
case "out of gas":
case "invalid opcode":
case "invalid JUMP":
case "stack overflow":
case "revert":
error = a[3]
break;
default:
if(a[3].startsWith("revert ")){
error = a[3].substring(7)
} else {
error = a[3];
}
}
}
}
exports.assertEVMException = async function(sendFn, error="") {
try {
await sendFn;
return false;
} catch (e) {
let strError = e.toString();
if(error == ""){
return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
} else {
return strError.includes(error);
}
}
};
exports.strictEVMException = async function(sendFn, error="") {
try {
await sendFn;
return false;
} catch (e) {
let strError = e.toString();
if(error == ""){
return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
} else {
return strError.includes(error);
}
}
};
function isException(error) {
let strError = error.toString();
return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
@ -207,33 +281,100 @@ function isException(error) {
exports.increaseTime = async (amount) => {
return new Promise(function(resolve, reject) {
web3.currentProvider.sendAsync(
web3.currentProvider.send(
{
jsonrpc: '2.0',
method: 'evm_increaseTime',
params: [+amount],
id: new Date().getSeconds()
},
async (error) => {
}, (error) => {
if (error) {
console.log(error);
return reject(err);
}
await web3.currentProvider.sendAsync(
{
reject(err);
} else {
web3.currentProvider.send({
jsonrpc: '2.0',
method: 'evm_mine',
params: [],
id: new Date().getSeconds()
}, (error) => {
if (error) {
console.log(error);
return reject(err);
}
reject(err);
}else {
resolve();
}
)
})
}
)
})
});
}
exports.setTime = async (timestamp) => {
return new Promise(function(resolve, reject) {
web3.currentProvider.send({
jsonrpc: '2.0',
method: 'evm_mine',
params: [+timestamp],
id: new Date().getSeconds()
}, (error,s)=>{
if(error) {
reject(error);
} else {
resolve();
}
})
})
}
exports.increaseBlock = (amount) => {
return new Promise(function(resolve, reject) {
web3.currentProvider.send(
{
jsonrpc: '2.0',
method: 'evm_mine',
params: [],
id: new Date().getSeconds()
},
(error,s)=>{
if(error) {
reject(error);
} else {
if(amount == 1) {
resolve()
}else {
exports.increaseBlock(amount-1).then(() => {
resolve();
})
}
}
})
})
}
exports.setBlockNumber = (newBlockNumber) => {
return new Promise((resolve, reject) => {
web3.eth.getBlockNumber().then((blockNumber) => {
if(blockNumber > newBlockNumber) {
reject("Cannot go back");
} else if (blockNumber < newBlockNumber) {
exports.increaseBlock(newBlockNumber - blockNumber).then(()=>{
resolve();
})
} else {
resolve();
}
})
})
}
exports.addGas = (call, from, amount=1010) => {
return new Promise((resolve, reject) => {
call.estimateGas().then((gas) => {
call.send({
gas: gas+amount,
from: from
}).on('error', reject).then(resolve);
})
})
}