feat: forfeit logic (#38)
This commit is contained in:
parent
af55ca0311
commit
5021d83482
|
@ -1,4 +1,10 @@
|
|||
const options = require("../app/js/contributors");
|
||||
let secret = {};
|
||||
try {
|
||||
secret = require('../.secret.json');
|
||||
} catch(err) {
|
||||
console.dir("warning: .secret.json file not found; this is only needed to deploy to testnet or livenet etc..");
|
||||
}
|
||||
|
||||
function getContributors () {
|
||||
var addresses = options.map(a => a.value);
|
||||
|
@ -201,10 +207,17 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
deployment: {
|
||||
accounts: [{
|
||||
mnemonic: "your mainnet mnemonic here",
|
||||
numAddresses: "10"
|
||||
}]
|
||||
accounts: [
|
||||
{
|
||||
mnemonic: secret.mnemonic,
|
||||
hdpath: secret.hdpath || "m/44'/60'/0'/0/",
|
||||
numAddresses: "10"
|
||||
}
|
||||
],
|
||||
host: `mainnet.infura.io/${secret.infuraKey}`,
|
||||
port: false,
|
||||
protocol: 'https',
|
||||
type: "rpc"
|
||||
},
|
||||
"afterDeploy": [
|
||||
// Add All Contributors
|
||||
|
|
|
@ -21,7 +21,7 @@ Extension:
|
|||
- /kudos 500 "<person>" "<praise>"
|
||||
*/
|
||||
|
||||
import "token/ERC20Token.sol";
|
||||
import "./token/ERC20Token.sol";
|
||||
|
||||
contract Meritocracy {
|
||||
|
||||
|
@ -50,6 +50,7 @@ contract Meritocracy {
|
|||
mapping(address => bool) public admins;
|
||||
mapping(address => Contributor) public contributors;
|
||||
bytes public contributorListIPFSHash;
|
||||
uint public SNTforfeitedBalance;
|
||||
|
||||
Meritocracy public previousMeritocracy; // Reference and read from previous contract
|
||||
|
||||
|
@ -74,13 +75,13 @@ contract Meritocracy {
|
|||
|
||||
// Functions only Owner can call
|
||||
modifier onlyOwner {
|
||||
require(msg.sender == owner);
|
||||
require(msg.sender == owner, "Only the owner can call this function");
|
||||
_;
|
||||
}
|
||||
|
||||
// Functions only Admin can call
|
||||
modifier onlyAdmin {
|
||||
require(admins[msg.sender]);
|
||||
require(admins[msg.sender], "Only admins can call this function");
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -93,14 +94,17 @@ contract Meritocracy {
|
|||
// Contributor memory cAllocator = contributors[msg.sender];
|
||||
// Requirements
|
||||
// require(cAllocator.addr != address(0)); // is sender a Contributor? TODO maybe relax this restriction.
|
||||
uint256 individualAmount = _amount / registry.length;
|
||||
uint256 individualAmount = (SNTforfeitedBalance + _amount) / registry.length;
|
||||
|
||||
// removing decimals
|
||||
individualAmount = (individualAmount / 1 ether * 1 ether);
|
||||
|
||||
uint amount = individualAmount * registry.length;
|
||||
uint amount = (individualAmount * registry.length) - SNTforfeitedBalance;
|
||||
|
||||
SNTforfeitedBalance = 0;
|
||||
|
||||
require(token.transferFrom(msg.sender, address(this), amount), "Couldn't transfer SNT");
|
||||
|
||||
require(token.transferFrom(msg.sender, address(this), amount));
|
||||
// Body
|
||||
// cAllocator.inPot = true;
|
||||
for (uint256 i = 0; i < registry.length; i++) {
|
||||
|
@ -119,9 +123,9 @@ contract Meritocracy {
|
|||
// Locals
|
||||
Contributor storage cReceiver = contributors[msg.sender];
|
||||
// Requirements
|
||||
require(cReceiver.addr == msg.sender); //is sender a Contributor?
|
||||
require(cReceiver.received > 0); // Contributor has received some tokens
|
||||
require(cReceiver.allocation == 0); // Contributor must allocate all Token (or have Token burnt) before they can withdraw.
|
||||
require(cReceiver.addr == msg.sender, "Not a contributor"); //is sender a Contributor?
|
||||
require(cReceiver.received > 0, "No tokens to withdraw"); // Contributor has received some tokens
|
||||
require(cReceiver.allocation == 0, "Allocation needs to be awarded or forfeited"); // Contributor must allocate all Token (or have Token burnt) before they can withdraw.
|
||||
// require(cReceiver.inPot); // Contributor has put some tokens into the pot
|
||||
// Body
|
||||
uint256 r = cReceiver.received;
|
||||
|
@ -236,7 +240,13 @@ contract Meritocracy {
|
|||
// Swap & Pop!
|
||||
registry[idx] = registry[registryLength];
|
||||
registry.pop();
|
||||
delete contributors[c]; // TODO check if this works
|
||||
|
||||
// Automatically withdraw ex-contributor SNT and increasing the forfeited balance
|
||||
Contributor storage contrib = contributors[c];
|
||||
token.transfer(contrib.addr, contrib.received);
|
||||
SNTforfeitedBalance += contrib.allocation;
|
||||
|
||||
delete contributors[c];
|
||||
// Set new IPFS hash for the list
|
||||
contributorListIPFSHash = _contributorListIPFSHash;
|
||||
emit ContributorRemoved(c);
|
||||
|
@ -260,6 +270,7 @@ contract Meritocracy {
|
|||
lastForfeit = block.timestamp;
|
||||
for (uint256 i = 0; i < registryLength; i++) { // should never be longer than maxContributors, see addContributor
|
||||
Contributor storage c = contributors[registry[i]];
|
||||
SNTforfeitedBalance += c.allocation;
|
||||
c.totalForfeited += c.allocation; // Shaaaaame!
|
||||
c.allocation = 0;
|
||||
// cReceiver.inPot = false; // Contributor has to put tokens into next round
|
||||
|
@ -335,10 +346,6 @@ contract Meritocracy {
|
|||
|
||||
// Constructor ------------------------------------------------------------------------------------------
|
||||
|
||||
// constructor(address _token, uint256 _maxContributors, address _previousMeritocracy) public {
|
||||
|
||||
// }
|
||||
|
||||
// Set Owner, Token address, initial maxContributors
|
||||
constructor(address _token, uint256 _maxContributors, bytes memory _contributorListIPFSHash) public {
|
||||
// Body
|
||||
|
@ -346,7 +353,7 @@ contract Meritocracy {
|
|||
addAdmin(owner);
|
||||
lastForfeit = block.timestamp;
|
||||
token = ERC20Token(_token);
|
||||
maxContributors= _maxContributors;
|
||||
maxContributors = _maxContributors;
|
||||
contributorListIPFSHash = _contributorListIPFSHash;
|
||||
// previousMeritocracy = Meritocracy(_previousMeritocracy);
|
||||
// importPreviousMeritocracyData() TODO
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"buildDir": "dist/",
|
||||
"config": "config/",
|
||||
"versions": {
|
||||
"solc": "0.5.0",
|
||||
"solc": "0.5.9",
|
||||
"ipfs-api": "17.2.4"
|
||||
},
|
||||
"plugins": {
|
||||
|
|
|
@ -31,7 +31,9 @@
|
|||
"@fortawesome/free-solid-svg-icons": "^5.8.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||
"bootstrap": "^4.3.1",
|
||||
"embark-solc": "^4.0.1",
|
||||
"embark": "^4.0.2",
|
||||
"embark-solc": "^4.0.3",
|
||||
"embark-solium": "0.1.0",
|
||||
"embarkjs-connector-web3": "^4.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"react": "^16.8.6",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*global contract, config, it, assert*/
|
||||
/* global contract, config, it, assert, web3, before, describe, xit */
|
||||
const Meritocracy = require('Embark/contracts/Meritocracy');
|
||||
// const StandardToken = require('Embark/contracts/StandardToken');
|
||||
const SNT = require('Embark/contracts/SNT');
|
||||
const TestUtils = require('../utils/testUtils');
|
||||
|
||||
let accounts;
|
||||
let owner;
|
||||
|
@ -24,7 +24,7 @@ config({
|
|||
]
|
||||
},
|
||||
contracts: {
|
||||
"MiniMeToken": {"deploy": false, "args": []},
|
||||
"MiniMeToken": {"deploy": false},
|
||||
"MiniMeTokenFactory": {},
|
||||
"SNT": {
|
||||
"instanceOf": "MiniMeToken",
|
||||
|
@ -32,14 +32,13 @@ config({
|
|||
"$MiniMeTokenFactory",
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
0,
|
||||
"TestMiniMeToken",
|
||||
"Status Network Token",
|
||||
18,
|
||||
"STT",
|
||||
true
|
||||
]
|
||||
},
|
||||
"Meritocracy": {
|
||||
"fromIndex": 0, // accounts[0]
|
||||
"args": ["$SNT", 10, IPFS_HASH] // Bind to SNT Contract, max 10 contributors.
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +46,7 @@ config({
|
|||
accounts = web3_accounts;
|
||||
owner = accounts[0];
|
||||
admins = [accounts[0], accounts[1], accounts[2]];
|
||||
ownerInitTokens = 10000;
|
||||
ownerInitTokens = web3.utils.toWei("10000", "ether");
|
||||
});
|
||||
|
||||
contract("Meritocracy", function () {
|
||||
|
@ -64,15 +63,14 @@ contract("Meritocracy", function () {
|
|||
assert.strictEqual(result, owner);
|
||||
|
||||
result = await Meritocracy.methods.maxContributors().call();
|
||||
assert.strictEqual(parseInt(result), 10);
|
||||
assert.strictEqual(parseInt(result, 10), 10);
|
||||
|
||||
});
|
||||
|
||||
it("registry.length == 3, allocate(1000);", async function () {
|
||||
var result;
|
||||
let allocationAmount = 1000;
|
||||
let allocationAmount = web3.utils.toWei("1000", "ether"); // 1000 SNT
|
||||
let contributorCount = 3;
|
||||
let individualAllocation = parseInt(allocationAmount / contributorCount); // 333
|
||||
|
||||
// Add 3 Contributors and check registry length matches
|
||||
var i = 0;
|
||||
|
@ -80,8 +78,9 @@ contract("Meritocracy", function () {
|
|||
result = await Meritocracy.methods.addContributor(accounts[i], IPFS_HASH).send({from: owner});
|
||||
i++;
|
||||
}
|
||||
|
||||
let registry = await Meritocracy.methods.getRegistry().call(); // TODO check if this works
|
||||
assert.strictEqual(parseInt(registry.length), contributorCount); // 3
|
||||
assert.strictEqual(parseInt(registry.length, 10), contributorCount); // 3
|
||||
|
||||
// Approve and allocate 1000 SNT for Meritocracy use
|
||||
result = await SNT.methods.approve(Meritocracy.options.address, allocationAmount).send({from: owner});
|
||||
|
@ -89,62 +88,201 @@ contract("Meritocracy", function () {
|
|||
|
||||
// FIXME these don't work. Looks like the allocation doesn't go through
|
||||
result = await SNT.methods.balanceOf(Meritocracy.address).call();
|
||||
// assert.strictEqual(parseInt(result), allocationAmount); // 1000
|
||||
assert.strictEqual(result, web3.utils.toWei("999", "ether")); // 999, because each contributor will receive 333.
|
||||
|
||||
result = await SNT.methods.balanceOf(owner).call();
|
||||
// assert.strictEqual(parseInt(result), ownerInitTokens - allocationAmount); // 9000
|
||||
assert.strictEqual(result, web3.utils.toBN(ownerInitTokens).sub(web3.utils.toBN(web3.utils.toWei("999", "ether"))).toString()); // 9001
|
||||
|
||||
// Check Individual Contributor amount is 333
|
||||
const contributor = await Meritocracy.methods.contributors(admins[0]).call();
|
||||
// assert.strictEqual(parseInt(contributor.allocation), individualAllocation); // 333
|
||||
});
|
||||
assert.strictEqual(contributor.allocation, web3.utils.toWei("333", "ether"));
|
||||
});
|
||||
|
||||
// TODO Addadmin
|
||||
// TODO RemoveAdmin
|
||||
|
||||
// TODO award
|
||||
describe('award', () => {
|
||||
it('should be able to award a contributor', async () => {
|
||||
await Meritocracy.methods.award(accounts[0], web3.utils.toWei("25", "ether"), "ABC").send({from: accounts[2]});
|
||||
|
||||
it("maxContributor + 1 fails", async function() {
|
||||
// TODO change so admin adds them
|
||||
var result;
|
||||
let contributorCount = 3;
|
||||
let additionalContributorsToMax = 7;
|
||||
var i = 0;
|
||||
while (i < additionalContributorsToMax) {
|
||||
result = await Meritocracy.methods.addContributor(accounts[contributorCount + i], IPFS_HASH).send({from: owner});
|
||||
i++;
|
||||
}
|
||||
try {
|
||||
result = await Meritocracy.methods.addContributor(accounts[i], IPFS_HASH).send({from: owner});
|
||||
assert.fail('should have reverted');
|
||||
} catch (error) {
|
||||
assert.strictEqual(error.message, "VM Exception while processing transaction: revert");
|
||||
}
|
||||
const sender = await Meritocracy.methods.contributors(accounts[2]).call();
|
||||
assert.strictEqual(sender.allocation, web3.utils.toBN(web3.utils.toWei("333", "ether")).sub(web3.utils.toBN(web3.utils.toWei("25", "ether"))).toString());
|
||||
|
||||
await Meritocracy.methods.award(accounts[1], web3.utils.toWei("25", "ether"), "ABC").send({from: accounts[2]});
|
||||
});
|
||||
|
||||
xit('should fail if awarded contributor does not exist', async () => {
|
||||
// TODO:
|
||||
});
|
||||
|
||||
xit('only contributors can do awards', async () => {
|
||||
// TODO:
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeContributor', () => {
|
||||
it('removes with normal values', async () => {
|
||||
let oldRegistry = await Meritocracy.methods.getRegistry().call();
|
||||
describe('withdraw', () => {
|
||||
it('cannot withdraw if user has allocation', async () => {
|
||||
try {
|
||||
await Meritocracy.methods.withdraw().send({from: accounts[0]});
|
||||
assert.fail('should have reverted');
|
||||
} catch (error) {
|
||||
assert.strictEqual(error.message, "VM Exception while processing transaction: revert Allocation needs to be awarded or forfeited");
|
||||
}
|
||||
});
|
||||
|
||||
let result = await Meritocracy.methods.removeContributor(1, IPFS_HASH).send({from: owner});
|
||||
it('can withdraw if allocation is 0', async () => {
|
||||
const balanceBefore = await SNT.methods.balanceOf(accounts[0]).call();
|
||||
const contributor = await Meritocracy.methods.contributors(accounts[0]).call();
|
||||
|
||||
let registry = await Meritocracy.methods.getRegistry().call();
|
||||
await Meritocracy.methods.award(accounts[2], web3.utils.toWei("333", "ether"), "ABC").send({from: accounts[0]});
|
||||
await Meritocracy.methods.withdraw().send({from: accounts[0]});
|
||||
|
||||
assert.strictEqual(registry.length, oldRegistry.length - 1);
|
||||
})
|
||||
})
|
||||
const balanceAfter = await SNT.methods.balanceOf(accounts[0]).call();
|
||||
|
||||
// TODO award
|
||||
// TODO withdraw before and after
|
||||
assert(web3.utils.toBN(balanceAfter).gt(web3.utils.toBN(balanceBefore)))
|
||||
assert.strictEqual(balanceAfter, web3.utils.toBN(balanceBefore).add(web3.utils.toBN(contributor.received)).toString());
|
||||
});
|
||||
});
|
||||
|
||||
// TODO forfeitAllocations
|
||||
describe('forfeitAllocations', () => {
|
||||
// TODO: add test cases
|
||||
let amountToForfeit = 0;
|
||||
let forfeitedBalance = 0;
|
||||
|
||||
it("should forfeit allocations", async () => {
|
||||
|
||||
const b1 = web3.utils.toBN((await Meritocracy.methods.contributors(accounts[0]).call()).allocation);
|
||||
const b2 = web3.utils.toBN((await Meritocracy.methods.contributors(accounts[1]).call()).allocation);
|
||||
const b3 = web3.utils.toBN((await Meritocracy.methods.contributors(accounts[2]).call()).allocation);
|
||||
|
||||
amountToForfeit = b1.add(b2.add(b3));
|
||||
|
||||
TestUtils.increaseTime(86400 * 8);
|
||||
|
||||
await Meritocracy.methods.forfeitAllocations().send({from: accounts[0]});
|
||||
});
|
||||
|
||||
it("Forfeited balance should increase", async () => {
|
||||
forfeitedBalance = await Meritocracy.methods.SNTforfeitedBalance().call();
|
||||
assert(forfeitedBalance !== "0");
|
||||
assert.strictEqual(forfeitedBalance, amountToForfeit.toString());
|
||||
});
|
||||
|
||||
it("Balances should be correct after forfeiting the allocations", async () => {
|
||||
const c1 = await Meritocracy.methods.contributors(accounts[0]).call();
|
||||
const c2 = await Meritocracy.methods.contributors(accounts[1]).call();
|
||||
const c3 = await Meritocracy.methods.contributors(accounts[2]).call();
|
||||
|
||||
const b1 = web3.utils.toBN(c1.allocation).add(web3.utils.toBN(c1.received));
|
||||
const b2 = web3.utils.toBN(c2.allocation).add(web3.utils.toBN(c2.received));
|
||||
const b3 = web3.utils.toBN(c3.allocation).add(web3.utils.toBN(c3.received));
|
||||
|
||||
const correctContractBalance = web3.utils.toBN(forfeitedBalance).add(b1.add(b2.add(b3)));
|
||||
const contractBalance = await SNT.methods.balanceOf(Meritocracy.options.address).call();
|
||||
|
||||
assert.strictEqual(correctContractBalance.toString(), contractBalance);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO withdraw after forfeitAllocations
|
||||
|
||||
// TODO setMaxContributors smaller than max
|
||||
// TODO setMaxContributors again
|
||||
// TODO addContributors
|
||||
// TODO changeOwner
|
||||
describe('allocate 2nd cycle', () => {
|
||||
|
||||
// TODO escape
|
||||
// TODO changeToken
|
||||
before(async () => {
|
||||
// Reset approval
|
||||
await SNT.methods.approve(Meritocracy.options.address, "0").send({from: owner});
|
||||
});
|
||||
|
||||
it("should allocate new funds", async () => {
|
||||
const allocationAmount = web3.utils.toWei("300", "ether"); // 300 SNT
|
||||
await SNT.methods.approve(Meritocracy.options.address, allocationAmount).send({from: owner});
|
||||
|
||||
await Meritocracy.methods.allocate(allocationAmount).send({from: owner});
|
||||
});
|
||||
|
||||
it("contract balance should be equivalent to new allocation (with prev forfeited balance), and received for each contributor", async () => {
|
||||
const contractBalance = await SNT.methods.balanceOf(Meritocracy.options.address).call();
|
||||
|
||||
const c1 = await Meritocracy.methods.contributors(accounts[0]).call();
|
||||
const c2 = await Meritocracy.methods.contributors(accounts[1]).call();
|
||||
const c3 = await Meritocracy.methods.contributors(accounts[2]).call();
|
||||
|
||||
const b1 = web3.utils.toBN(c1.allocation).add(web3.utils.toBN(c1.received));
|
||||
const b2 = web3.utils.toBN(c2.allocation).add(web3.utils.toBN(c2.received));
|
||||
const b3 = web3.utils.toBN(c3.allocation).add(web3.utils.toBN(c3.received));
|
||||
|
||||
const forfeitedBalance = web3.utils.toBN(await Meritocracy.methods.SNTforfeitedBalance().call());
|
||||
|
||||
const correctContractBalance = forfeitedBalance.add(b1.add(b2.add(b3)));
|
||||
assert.strictEqual(contractBalance, correctContractBalance.toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeContributor', () => {
|
||||
let initialForfeitBalance = 0;
|
||||
let balanceToForfeit = 0;
|
||||
let contribBalance = 0;
|
||||
let contribReceived = 0;
|
||||
|
||||
before(async () => {
|
||||
initialForfeitBalance = web3.utils.toBN(await Meritocracy.methods.SNTforfeitedBalance().call());
|
||||
const contributor = await Meritocracy.methods.contributors(accounts[1]).call();
|
||||
balanceToForfeit = web3.utils.toBN(contributor.allocation);
|
||||
contribReceived = web3.utils.toBN(contributor.received);
|
||||
contribBalance = web3.utils.toBN(await SNT.methods.balanceOf(accounts[1]).call());
|
||||
});
|
||||
|
||||
it('removes with normal values', async () => {
|
||||
let oldRegistry = await Meritocracy.methods.getRegistry().call();
|
||||
await Meritocracy.methods.removeContributor(1, IPFS_HASH).send({from: owner});
|
||||
let registry = await Meritocracy.methods.getRegistry().call();
|
||||
assert.strictEqual(registry.length, oldRegistry.length - 1);
|
||||
});
|
||||
|
||||
it("contributor should have received their SNT", async () => {
|
||||
assert(contribReceived !== '0');
|
||||
|
||||
const currBalance = await SNT.methods.balanceOf(accounts[1]).call();
|
||||
assert.strictEqual(currBalance, contribBalance.add(contribReceived).toString());
|
||||
});
|
||||
|
||||
it("contributor data should be removed", async() => {
|
||||
const contributor = await Meritocracy.methods.contributors(accounts[1]).call();
|
||||
assert.strictEqual(TestUtils.zeroAddress, contributor.addr);
|
||||
});
|
||||
|
||||
it("forfeited balance should have increased using deleted contributor allocation", async () => {
|
||||
const forfeitedBalance = await Meritocracy.methods.SNTforfeitedBalance().call();
|
||||
assert.strictEqual(forfeitedBalance, initialForfeitBalance.add(balanceToForfeit).toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('other conditions', async () => {
|
||||
it("maxContributor + 1 fails", async function() {
|
||||
// TODO change so admin adds them
|
||||
let contributorCount = 3;
|
||||
let additionalContributorsToMax = 7;
|
||||
var i = 0;
|
||||
while (i < additionalContributorsToMax) {
|
||||
await Meritocracy.methods.addContributor(accounts[contributorCount + i], IPFS_HASH).send({from: owner});
|
||||
i++;
|
||||
}
|
||||
try {
|
||||
await Meritocracy.methods.addContributor(accounts[i], IPFS_HASH).send({from: owner});
|
||||
assert.fail('should have reverted');
|
||||
} catch (error) {
|
||||
assert.strictEqual(error.message, "VM Exception while processing transaction: revert");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// TODO setMaxContributors smaller than max
|
||||
// TODO setMaxContributors again
|
||||
// TODO addContributors
|
||||
// TODO changeOwner
|
||||
|
||||
// TODO escape
|
||||
// TODO changeToken
|
||||
// TODO escape overload?
|
||||
});
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/*global assert, web3*/
|
||||
|
||||
// This has been tested with the real Ethereum network and Testrpc.
|
||||
// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
|
||||
exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(contractMethodCall());
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
.then(tx => {
|
||||
assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed");
|
||||
})
|
||||
.catch(error => {
|
||||
if ((String(error)).indexOf("invalid opcode") < 0 && (String(error)).indexOf("out of gas") < 0) {
|
||||
// Checks if the error is from TestRpc. If it is then ignore it.
|
||||
// Otherwise relay/throw the error produced by the above assertion.
|
||||
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.listenForEvent = event => new Promise((resolve, reject) => {
|
||||
event({}, (error, response) => {
|
||||
if (!error) {
|
||||
resolve(response.args);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
event.stopWatching();
|
||||
});
|
||||
});
|
||||
|
||||
exports.eventValues = (receipt, eventName) => {
|
||||
if (receipt.events[eventName]) return receipt.events[eventName].returnValues;
|
||||
};
|
||||
|
||||
exports.addressToBytes32 = (address) => {
|
||||
const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2);
|
||||
return "0x" + stringed.substring(stringed.length - 64, stringed.length);
|
||||
};
|
||||
|
||||
// OpenZeppelin's expectThrow helper -
|
||||
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
|
||||
exports.expectThrow = async promise => {
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
// TODO: Check jump destination to destinguish between a throw
|
||||
// and an actual invalid jump.
|
||||
const invalidOpcode = error.message.search('invalid opcode') >= 0;
|
||||
// TODO: When we contract A calls contract B, and B throws, instead
|
||||
// of an 'invalid jump', we get an 'out of gas' error. How do
|
||||
// we distinguish this from an actual out of gas event? (The
|
||||
// testrpc log actually show an 'invalid jump' event.)
|
||||
const outOfGas = error.message.search('out of gas') >= 0;
|
||||
const revert = error.message.search('revert') >= 0;
|
||||
assert(
|
||||
invalidOpcode || outOfGas || revert,
|
||||
'Expected throw, got \'' + error + '\' instead',
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert.fail('Expected throw not received');
|
||||
};
|
||||
|
||||
|
||||
exports.assertJump = (error) => {
|
||||
assert(error.message.search('VM Exception while processing transaction') > -1, 'Revert should happen');
|
||||
};
|
||||
|
||||
|
||||
function callbackToResolve(resolve, reject) {
|
||||
return function(error, value) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.promisify = (func) =>
|
||||
(...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (err, data) => err ? reject(err) : resolve(data);
|
||||
func.apply(this, [...args, callback]);
|
||||
});
|
||||
};
|
||||
|
||||
exports.zeroAddress = '0x0000000000000000000000000000000000000000';
|
||||
exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
exports.timeUnits = {
|
||||
seconds: 1,
|
||||
minutes: 60,
|
||||
hours: 60 * 60,
|
||||
days: 24 * 60 * 60,
|
||||
weeks: 7 * 24 * 60 * 60,
|
||||
years: 365 * 24 * 60 * 60
|
||||
};
|
||||
|
||||
exports.ensureException = function(error) {
|
||||
assert(isException(error), error.toString());
|
||||
};
|
||||
|
||||
function isException(error) {
|
||||
let strError = error.toString();
|
||||
return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
|
||||
}
|
||||
|
||||
const evmMethod = (method, params = []) => {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const sendMethod = (web3.currentProvider.sendAsync) ? web3.currentProvider.sendAsync.bind(web3.currentProvider) : web3.currentProvider.send.bind(web3.currentProvider);
|
||||
sendMethod(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params,
|
||||
id: new Date().getSeconds()
|
||||
},
|
||||
(error, res) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(res.result);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
exports.evmSnapshot = async () => {
|
||||
const result = await evmMethod("evm_snapshot");
|
||||
return web3.utils.hexToNumber(result);
|
||||
};
|
||||
|
||||
exports.evmRevert = (id) => {
|
||||
const params = [id];
|
||||
return evmMethod("evm_revert", params);
|
||||
};
|
||||
|
||||
exports.increaseTime = async (amount) => {
|
||||
await evmMethod("evm_increaseTime", [Number(amount)]);
|
||||
await evmMethod("evm_mine");
|
||||
};
|
Loading…
Reference in New Issue