Skip to content

Commit

Permalink
Merge 1f36f58 into c63b203
Browse files Browse the repository at this point in the history
  • Loading branch information
k06a committed Apr 18, 2018
2 parents c63b203 + 1f36f58 commit fbc4d85
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
pragma solidity ^0.4.21;

import { MerkleProof } from "../../MerkleProof.sol";
import "../../math/SafeMath.sol";
import "../../ownership/Ownable.sol";
import "../Crowdsale.sol";


/**
* @title MerkleIndividuallyCappedCrowdsale
* @dev Crowdsale with per-user caps stored as Merkle tree.
*/
contract MerkleIndividuallyCappedCrowdsale is Crowdsale, Ownable {
mapping(address => uint256) public contributions;
bytes32 public capsMerkleRoot;

/**
* @dev Sets a overall users maximum contributions
* @param _capsMerkleRoot Root of the merkle tree
*/
function setCapsMerkleRoot(bytes32 _capsMerkleRoot) public onlyOwner {
capsMerkleRoot = _capsMerkleRoot;
}

/**
* @dev Override parent behavior to deny this method usage
* @param _beneficiary Token purchaser
*/
function buyTokens(address _beneficiary) public payable {
revert();
}

/**
* @dev Extend parent behavior to update user contributions
* @param _beneficiary Token purchaser
* @param _individualCap Beneficiary personal cap in wei
* @param _proof Merkle proof of personal cap
*/
function buyTokens(address _beneficiary, uint256 _individualCap, bytes32[] _proof) public payable {
require(contributions[_beneficiary] + msg.value <= _individualCap);
bytes32 leaf = keccak256(_beneficiary, _individualCap);
require(MerkleProof.verifyProof(_proof, capsMerkleRoot, leaf));

contributions[_beneficiary] += msg.value;
super.buyTokens(_beneficiary);
}

}
19 changes: 19 additions & 0 deletions contracts/mocks/MerkleIndividuallyCappedCrowdsaleImpl.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pragma solidity ^0.4.21;

import "../crowdsale/validation/MerkleIndividuallyCappedCrowdsale.sol";
import "../token/ERC20/ERC20.sol";


contract MerkleIndividuallyCappedCrowdsaleImpl is MerkleIndividuallyCappedCrowdsale {

function MerkleIndividuallyCappedCrowdsaleImpl(
uint256 _rate,
address _wallet,
ERC20 _token
)
public
Crowdsale(_rate, _wallet, _token)
{
}

}
149 changes: 149 additions & 0 deletions test/crowdsale/MerkleIndividuallyCappedCrowdsale.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import ether from '../helpers/ether';
import EVMRevert from '../helpers/EVMRevert';
import MerkleTree from '../helpers/merkleTree.js';

const MerkleProof = artifacts.require('MerkleProof');
const MintableToken = artifacts.require('MintableToken');
const MerkleIndividuallyCappedCrowdsale = artifacts.require('MerkleIndividuallyCappedCrowdsaleImpl');

function padLeft (s, n, str) {
return Array(n - String(s).length + 1).join(str || '0') + s;
}

contract('MerkleIndividuallyCappedCrowdsale', function ([_, wallet1, wallet2, wallet3, wallet4, wallet5, wallet6]) {
const caps = {
[wallet1]: ether(10),
[wallet2]: ether(20),
[wallet3]: ether(30),
[wallet4]: ether(40),
[wallet5]: ether(50),
[wallet6]: ether(60),
};
const elements = Object.keys(caps).map(key =>
Buffer.from(key.substr(2) + padLeft(caps[key].toString(16), 64), 'hex'));
let merkleTree = new MerkleTree(elements);

let crowdsale;

before(async function () {
MerkleIndividuallyCappedCrowdsale.link('MerkleProof', (await MerkleProof.new()).address);
});

beforeEach(async function () {
const token = await MintableToken.new();
crowdsale = await MerkleIndividuallyCappedCrowdsale.new(1, _, token.address);
await crowdsale.setCapsMerkleRoot(merkleTree.getHexRoot());
await token.mint(crowdsale.address, 1000000 * (10 ** 18));
});

it('should reject calls on old method', async function () {
let reverted = false;
try {
await crowdsale.contract.buyTokens.address(wallet1, { value: 40, from: _ });
} catch (e) {
reverted = (e.message === 'VM Exception while processing transaction: revert');
}
reverted.should.be.true;
});

it('should fail on wrong proofs', async function () {
await crowdsale.buyTokens(wallet1, ether(10), merkleTree.getHexProof(elements[1]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet2, ether(20), merkleTree.getHexProof(elements[2]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet3, ether(30), merkleTree.getHexProof(elements[3]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet4, ether(40), merkleTree.getHexProof(elements[4]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet5, ether(50), merkleTree.getHexProof(elements[0]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
});

it('should fail on wrong caps', async function () {
await crowdsale.buyTokens(wallet1, ether(20), merkleTree.getHexProof(elements[0]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet2, ether(10), merkleTree.getHexProof(elements[1]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet3, ether(50), merkleTree.getHexProof(elements[2]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet4, ether(30), merkleTree.getHexProof(elements[3]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet5, ether(40), merkleTree.getHexProof(elements[4]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
});

it('should fail on wrong wallets', async function () {
await crowdsale.buyTokens(wallet2, ether(10), merkleTree.getHexProof(elements[0]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet1, ether(20), merkleTree.getHexProof(elements[1]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet5, ether(30), merkleTree.getHexProof(elements[2]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet3, ether(40), merkleTree.getHexProof(elements[3]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet4, ether(50), merkleTree.getHexProof(elements[4]), { value: ether(4) })
.should.be.rejectedWith(EVMRevert);
});

it('should work fine', async function () {
// Wallet1
await crowdsale.buyTokens(wallet1, ether(10), merkleTree.getHexProof(elements[0]), { value: ether(11) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet1, ether(10), merkleTree.getHexProof(elements[0]), { value: ether(4) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet1, ether(10), merkleTree.getHexProof(elements[0]), { value: ether(7) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet1, ether(10), merkleTree.getHexProof(elements[0]), { value: ether(6) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet1, ether(10), merkleTree.getHexProof(elements[0]), { value: ether(1) })
.should.be.rejectedWith(EVMRevert);

// Wallet2
await crowdsale.buyTokens(wallet2, ether(20), merkleTree.getHexProof(elements[1]), { value: ether(21) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet2, ether(20), merkleTree.getHexProof(elements[1]), { value: ether(4) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet2, ether(20), merkleTree.getHexProof(elements[1]), { value: ether(17) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet2, ether(20), merkleTree.getHexProof(elements[1]), { value: ether(16) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet2, ether(20), merkleTree.getHexProof(elements[1]), { value: ether(1) })
.should.be.rejectedWith(EVMRevert);

// Wallet3
await crowdsale.buyTokens(wallet3, ether(30), merkleTree.getHexProof(elements[2]), { value: ether(31) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet3, ether(30), merkleTree.getHexProof(elements[2]), { value: ether(4) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet3, ether(30), merkleTree.getHexProof(elements[2]), { value: ether(27) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet3, ether(30), merkleTree.getHexProof(elements[2]), { value: ether(26) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet3, ether(30), merkleTree.getHexProof(elements[2]), { value: ether(1) })
.should.be.rejectedWith(EVMRevert);

// Wallet4
await crowdsale.buyTokens(wallet4, ether(40), merkleTree.getHexProof(elements[3]), { value: ether(41) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet4, ether(40), merkleTree.getHexProof(elements[3]), { value: ether(4) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet4, ether(40), merkleTree.getHexProof(elements[3]), { value: ether(37) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet4, ether(40), merkleTree.getHexProof(elements[3]), { value: ether(36) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet4, ether(40), merkleTree.getHexProof(elements[3]), { value: ether(1) })
.should.be.rejectedWith(EVMRevert);

// Wallet5
await crowdsale.buyTokens(wallet5, ether(50), merkleTree.getHexProof(elements[4]), { value: ether(51) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet5, ether(50), merkleTree.getHexProof(elements[4]), { value: ether(4) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet5, ether(50), merkleTree.getHexProof(elements[4]), { value: ether(47) })
.should.be.rejectedWith(EVMRevert);
await crowdsale.buyTokens(wallet5, ether(50), merkleTree.getHexProof(elements[4]), { value: ether(46) })
.should.be.fulfilled;
await crowdsale.buyTokens(wallet5, ether(50), merkleTree.getHexProof(elements[4]), { value: ether(1) })
.should.be.rejectedWith(EVMRevert);
});
});

0 comments on commit fbc4d85

Please sign in to comment.