Skip to content

Commit

Permalink
adapt, fix and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Federico Martín Alconada Verzini committed Nov 9, 2017
1 parent 5ceb757 commit 9691ca8
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 81 deletions.
8 changes: 5 additions & 3 deletions contracts/Crowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import "./QiibeeToken.sol";
@title Crowdsale for the QBX Token Generation Event
Implementation of kind of an 'abstract' QBX Token Generation Event (TGE). This contract will be
used by QiibeePresale.sol and QiibeeCrowdsale.
used by QiibeePresale.sol and QiibeeCrowdsale.sol
This TGE includes is capped and has a spam prevention technique:
This TGE is capped and has a spam prevention technique:
* investors can make purchases with a minimum request inverval of X seconds given by minBuyingRequestInterval.
* investors are limited in the gas price
In case of the goal not being reached by purchases made during the 4-week period the token will
not start operating and all funds sent during that period will be made available to be claimed
by the originating addresses.
The function buyTokens() is not minting tokens. This function should be overriden to add that logic.
*/

contract Crowdsale is Pausable {
Expand Down Expand Up @@ -114,7 +116,7 @@ contract Crowdsale is Pausable {
}

/**
* @dev Must be overridden to add buy token minting logic. The overriding function
* @dev Must be overridden to add token minting logic. The overriding function
* should call super.finalization() to ensure the chain of buy tokens is
* executed entirely.
*/
Expand Down
2 changes: 1 addition & 1 deletion contracts/QiibeePresale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ contract QiibeePresale is Crowdsale {
if (data.cliff > 0 && data.vesting > 0) {
assert(token.mintVestedTokens(beneficiary, tokens, from, cliff, vesting, revokable, burnsOnRevoke));
} else {
assert(token.mint(this, tokens));
assert(token.mint(beneficiary, tokens));
}

// update state
Expand Down
8 changes: 3 additions & 5 deletions contracts/QiibeeToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ contract QiibeeToken is BurnableToken, PausableToken, VestedToken, MintableToken
}

/**
@dev Overrides VestedToken#grantVestedTokens(). Similar to grantVestedTokens but minting tokens
instead of transferring.
@dev Similar to grantVestedTokens but minting tokens instead of transferring.
*/
function mintVestedTokens (
address _to,
Expand All @@ -42,7 +41,7 @@ contract QiibeeToken is BurnableToken, PausableToken, VestedToken, MintableToken
uint64 _vesting,
bool _revokable,
bool _burnsOnRevoke
) onlyOwner public constant returns (bool) {
) onlyOwner public returns (bool) {
// Check for date inconsistencies that may cause unexpected behavior
require(_cliff >= _start && _vesting >= _cliff);

Expand All @@ -61,8 +60,7 @@ contract QiibeeToken is BurnableToken, PausableToken, VestedToken, MintableToken
);

bool minted = mint(_to, _value); //mint tokens

NewTokenGrant(msg.sender, _to, _value, count - 1);
Mint(_to, _value);

return minted;
}
Expand Down
3 changes: 1 addition & 2 deletions contracts/WhitelistedCrowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ contract WhitelistedCrowdsale is Crowdsale, Ownable {

// @return true if investor is whitelisted
function isWhitelisted(address investor) public constant returns (bool) {
require(investor != address(0));
return whitelist[investor];
}

// @return true if buyer investor been removed
function removeFromWhitelisted(address investor) public onlyOwner {
function removeFromWhitelist(address investor) public onlyOwner {
require(investor != address(0));
whitelist[investor] = false;
}
Expand Down
63 changes: 38 additions & 25 deletions test/PresaleGenTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'presaleSendTransaction', beneficiary: 3, account: 4, eth: 1 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
}
});
});

it('should allow accredited investors to buy tokens', async function () {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'presaleSendTransaction', beneficiary: 3, account: 4, eth: 1 },
],
presale: {
Expand All @@ -190,7 +177,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 0, vesting: 0, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 0, vesting: 0, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'presaleSendTransaction', beneficiary: 3, account: 4, eth: 1 },
],
presale: {
Expand All @@ -203,7 +190,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'presaleBuyTokens', beneficiary: 3, account: 4, eth: 3 },
],
presale: {
Expand All @@ -216,7 +203,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'presaleBuyTokens', beneficiary: 3, account: 4, eth: 0.5 },
],
presale: {
Expand All @@ -238,11 +225,12 @@ contract('QiibeePresale property-based test', function(accounts) {
});

describe('add to accredited list', function () {

it('should NOT be able to add investor to accredited list with rate zero', async function () {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 0, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
{ type: 'addAccredited', investor: 4, rate: 0, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
Expand All @@ -254,7 +242,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: -600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: -600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
Expand All @@ -266,7 +254,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 0, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 0, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
Expand All @@ -278,7 +266,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: -600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: -600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
Expand All @@ -290,7 +278,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: -2, fromAccount: 2 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: -2, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
Expand All @@ -302,7 +290,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, minInvest: 0, maxCumulativeInvest: 2, fromAccount: 2 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 0, maxCumulativeInvest: 2, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
Expand All @@ -314,7 +302,7 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
{ type: 'addAccredited', investor: 4, rate: 6000, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
Expand All @@ -326,13 +314,38 @@ contract('QiibeePresale property-based test', function(accounts) {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'addAccredited', investor: 'zero', rate: 6000, cliff: 600, vesting: 600, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
{ type: 'addAccredited', investor: 'zero', rate: 6000, cliff: 600, vesting: 600, revokable: false, burnsOnTokens: false, minInvest: 1, maxCumulativeInvest: 2, fromAccount: 0 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
}
});
});

it('should be able to remove investor from accredited list', async function () {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'removeAccredited', investor: 4, fromAccount: 0 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
}
});
});

it('should NOT be able to remove investor from accredited list if not owner', async function () {
await runGeneratedPresaleAndCommands({
commands: [
{ type: 'waitTime','seconds':duration.days(1)},
{ type: 'removeAccredited', investor: 4, fromAccount: 2 },
],
presale: {
maxGasPrice: 50000000000, minBuyingRequestInterval: 600, goal: 36000, cap: 240000, foundationWallet: 10, owner: 0
}
});
});

});

it('distributes tokens correctly on any combination of bids', async function() {
Expand Down
23 changes: 23 additions & 0 deletions test/WhitelistedCrowdsale.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,30 @@ contract('WhitelistedCrowdsale', function([owner, wallet, beneficiary, sender])
assert.equal(false, await crowdsale.isWhitelisted(sender));
await crowdsale.addToWhitelist(sender, {from: owner});
assert.equal(true, await crowdsale.isWhitelisted(sender));
});

it('should remove address from whitelist', async function () {
assert.equal(false, await crowdsale.isWhitelisted(sender));
await crowdsale.addToWhitelist(sender, {from: owner});
assert.equal(true, await crowdsale.isWhitelisted(sender));
await crowdsale.removeFromWhitelist(sender, {from: owner});
assert.equal(false, await crowdsale.isWhitelisted(sender));
});

it('should reject adding zero address to whitelist', async function () {
try {
await crowdsale.addToWhitelist('0x0000000000000000000000000000000000000000', {from: owner});
} catch (e) {
assertExpectedException(e);
}
});

it('should reject removing zero address from whitelist', async function () {
try {
await crowdsale.removeFromWhitelist('0x0000000000000000000000000000000000000000', {from: owner});
} catch (e) {
assertExpectedException(e);
}
});

it('should reject non-whitelisted sender', async function () {
Expand Down
30 changes: 28 additions & 2 deletions test/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ async function runPresaleSendTransactionCommand(command, state) {
frequencyExceeded ||
gasExceeded ||
capExceeded;

try {
const tx = await state.presaleContract.sendTransaction({value: weiCost, from: account});
assert.equal(false, shouldThrow, 'sendTransaction should have thrown but it did not');
Expand Down Expand Up @@ -360,6 +361,8 @@ async function runAddAccreditedCommand(command, state) {
rate = command.rate,
cliff = command.cliff,
vesting = command.vesting,
revokable = command.revokable,
burnsOnTokens = command.burnsOnTokens,
minInvest = command.minInvest,
maxCumulativeInvest = command.maxCumulativeInvest;

Expand All @@ -374,18 +377,40 @@ async function runAddAccreditedCommand(command, state) {
command.fromAccount != state.owner;

try {
await state.presaleContract.addAccreditedInvestor(investor, rate, cliff, vesting, help.toWei(minInvest), help.toWei(maxCumulativeInvest), {from: account});
await state.presaleContract.addAccreditedInvestor(investor, rate, cliff, vesting, revokable, burnsOnTokens, help.toWei(minInvest), help.toWei(maxCumulativeInvest), {from: account});
help.debug(colors.green('SUCCESS adding accredited investor'));

assert.equal(false, shouldThrow, 'add to whitelist should have thrown but it did not');
state.accredited[command.investor] = {rate: rate, cliff: cliff, vesting: vesting, minInvest: help.toWei(minInvest), maxCumulativeInvest: help.toWei(maxCumulativeInvest)};
state.accredited[command.investor] = {rate: rate, cliff: cliff, vesting: vesting, revokable, burnsOnTokens, minInvest: help.toWei(minInvest), maxCumulativeInvest: help.toWei(maxCumulativeInvest)};
} catch(e) {
assertExpectedException(e, shouldThrow, hasZeroAddress, state, command);
help.debug(colors.yellow('FAILURE adding accredited investor'));
}
return state;
}

async function runRemoveAccreditedCommand(command, state) {
let account = gen.getAccount(command.fromAccount),
investor = gen.getAccount(command.investor);

let hasZeroAddress = _.some([account, investor], isZeroAddress);

let shouldThrow = hasZeroAddress ||
command.fromAccount != state.owner;

try {
await state.presaleContract.removeAccreditedInvestor(investor, {from: account});
help.debug(colors.green('SUCCESS removing accredited investor'));

assert.equal(false, shouldThrow, 'add to whitelist should have thrown but it did not');
delete state.accredited[command.investor];
} catch(e) {
assertExpectedException(e, shouldThrow, hasZeroAddress, state, command);
help.debug(colors.yellow('FAILURE removing accredited investor'));
}
return state;
}

async function runPauseCrowdsaleCommand(command, state) {
let account = gen.getAccount(command.fromAccount),
hasZeroAddress = isZeroAddress(account);
Expand Down Expand Up @@ -689,6 +714,7 @@ const presaleCommands = {
presaleSendTransaction: {gen: gen.presaleSendTransactionCommandGen, run: runPresaleSendTransactionCommand},
pausePresale: {gen: gen.pausePresaleCommandGen, run: runPausePresaleCommand},
addAccredited: { gen: gen.addAccreditedCommandGen, run: runAddAccreditedCommand},
removeAccredited: { gen: gen.removeAccreditedCommandGen, run: runRemoveAccreditedCommand},
finalizePresale: {gen: gen.finalizePresaleCommandGen, run: runFinalizePresaleCommand}
};

Expand Down
8 changes: 8 additions & 0 deletions test/generators.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,17 @@ module.exports = {
rate: jsc.integer(0, 20000),
cliff: jsc.integer(0, 20000),
vesting: jsc.integer(0, 20000),
revokable: jsc.bool,
burnsOnTokens: jsc.bool,
minInvest: jsc.integer(0, 1000000000),
maxCumulativeInvest: jsc.integer(0, 1000000000),
fromAccount: accountGen,
}),

removeAccreditedCommandGen: jsc.record({
type: jsc.constant('removeAccredited'),
investor: knownAccountGen,
fromAccount: accountGen,
}),
};

8 changes: 5 additions & 3 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ module.exports = {
return this.waitToBlock(parseInt(web3.eth.blockNumber) + toWait, accounts);
},

simulateCrowdsale: async function(rate, goal, cap, minInvest, maxCumulativeInvest, maxGasPrice, minBuyingRequestInterval, accounts, balances) {
simulateCrowdsale: async function(rate, goal, cap, minInvest, maxCumulativeInvest, maxGasPrice, minBuyingRequestInterval, accounts, balances, finish) {
await increaseTimeTestRPC(1);
var startTime = latestTime() + 5;
var endTime = startTime + 10;
Expand All @@ -104,8 +104,10 @@ module.exports = {
await crowdsale.sendTransaction({ value: web3.toWei(balances[i], 'ether'), from: accounts[i + 1]});
}
}
await increaseTimeTestRPCTo(endTime+1);
await crowdsale.finalize();
if (finish) {
await increaseTimeTestRPCTo(endTime+1);
await crowdsale.finalize();
}
return crowdsale;
},

Expand Down

0 comments on commit 9691ca8

Please sign in to comment.