add whitelist

This commit is contained in:
Roman Storm 2017-11-11 19:36:41 -08:00
parent 3bbd8f0887
commit a8810b5619
10 changed files with 4092 additions and 37 deletions

115
README.md
View File

@ -27,57 +27,99 @@ solidity_flattener --solc-paths=zeppelin-solidity=$(pwd)/node_modules/zeppelin-s
Example:
"1510291574","1610291574","100000000000000000000","100000000000000000","0x0039f22efb07a647557c7c5d17854cfd6d489ef3"
startTime: `Friday, November 10, 2017 5:26:14 AM `
endTime: `Sunday, January 10, 2021 3:12:54 PM `
cap: `100 eth `
minimum: `0.1 eth `
vault: `0x0039f22efb07a647557c7c5d17854cfd6d489ef3`
4. Let investors send money to contract's address OR to send to `buy` method (`0xa6f2ae3a`)
4. Whitelist investors by calling `whitelistInvestors` with an array ['0x0039f22efb07a647557c7c5d17854cfd6d489ef3']
5. Let whitelisted investors send money to contract's address OR to send to `buy` method (`0xa6f2ae3a`)
# How to whitelist a lot of addresses in batch
1. `cd scripts`
2. `npm install`
3. open `.env` file and modify settings:
```
Contract: Presale
PRESALE_ADDRESS=0x6F3f79941f89E03D4aF9bDb8BE0623DC24F2bef0
UNLOCKED_ADDRESS=0x0039f22efb07a647557c7c5d17854cfd6d489ef3
RPC_PORT=8549
GAS_PRICE=0.7
```
4. run parity with unlocked account from which the deployment will happen. It has to match with your .env UNLOCKED_ADDRESS:
```
parity --jsonrpc-port 8549 --chain kovan --unlock 0x0039F22efB07A647557C7C5d17854CFD6D489eF3 --password $HOME/FILE_PATH_TO_YOUR_PASSWORD_FILE
```
4. run `node whitelist.js`
# Test result and gas usage
```
Contract: Presale
✓ constructor should set owner
✓ can not buy if not initialized (22623 gas)
✓ can not buy if not initialized (21411 gas)
#initilize
✓ rejects if not sent by owner (25358 gas)
✓ sets values (131519 gas)
✓ cannot initialize twice (157189 gas)
✓ startTime cannot be 0 (25102 gas)
✓ endTime cannot be 0 (25166 gas)
✓ endTime cannot be less than startTime (25358 gas)
✓ cap cannot be 0 (24910 gas)
✓ vault cannot be 0x0 (24078 gas)
✓ minimumContribution cannot be 0 (24910 gas)
✓ rejects if not sent by owner (25424 gas)
✓ sets values (131614 gas)
✓ cannot initialize twice (157350 gas)
✓ startTime cannot be 0 (25168 gas)
✓ endTime cannot be 0 (25232 gas)
✓ endTime cannot be less than startTime (25424 gas)
✓ cap cannot be 0 (24976 gas)
✓ vault cannot be 0x0 (24144 gas)
✓ minimumContribution cannot be 0 (24976 gas)
#buy
✓ cannot buy if not value is 0 (64231 gas)
✓ cannot buy if not value is less than minimum (64231 gas)
✓ can not buy if time is not within startTime&endTime (116233 gas)
✓ can not buy more than cap (64236 gas)
✓ happy path (178974 gas)
✓ cannot buy if not whitelisted (21411 gas)
✓ cannot buy if not value is 0 (63333 gas)
✓ cannot buy if not value is less than minimum (63333 gas)
✓ can not buy if time is not within startTime&endTime (111666 gas)
✓ can not buy more than cap (63333 gas)
✓ happy path (244544 gas)
whitelisting capabilities
#whitelistInvestor
✓ cannot by called by non-owner (23450 gas)
✓ whitelists an investor (64399 gas)
#whitelistInvestors
✓ cannot by called by non-owner (23666 gas)
✓ whitelists investors (119844 gas)
#blacklistInvestor
✓ cannot by called by non-owner (23582 gas)
✓ blacklist an investors (139579 gas)
·-----------------------------------------------------------------------|-----------------------------------·
│ Gas · Block limit: 17592186044415 gas │
·········································|······························|····································
│ Methods · 1 gwei/gas · 297.51 usd/eth │
···················|·····················|·········|··········|·········|················|···················
│ Contract · Method · Min · Max · Avg · # calls · usd (avg) │
···················|·····················|·········|··········|·········|················|···················
│ PresaleOracles · buy · - · - · - · 0 · - │
···················|·····················|·········|··········|·········|················|···················
│ PresaleOracles · claimTokens · - · - · - · 0 · - │
···················|·····················|·········|··········|·········|················|···················
│ PresaleOracles · initialize · 24078 · 131519 · 46359 · 10 · 0.01 │
···················|·····················|·········|··········|·········|················|···················
│ PresaleOracles · Presale · - · - · - · 0 · - │
···················|·····················|·········|··········|·········|················|···················
│ PresaleOracles · transferOwnership · - · - · - · 0 · - │
·------------------|---------------------|---------|----------|---------|----------------|------------------·
·------------------------------------------------------------------------|-----------------------------------·
│ Gas · Block limit: 17592186044415 gas │
··········································|······························|····································
│ Methods · 1 gwei/gas · 307.55 usd/eth │
···················|······················|·········|··········|·········|················|···················
│ Contract · Method · Min · Max · Avg · # calls · usd (avg) │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · blacklistInvestor · 19735 · 23582 · 21659 · 2 · 0.01 │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · buy · - · - · - · 0 · - │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · claimTokens · - · - · - · 0 · - │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · initialize · 24144 · 131614 · 46431 · 10 · 0.01 │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · Presale · - · - · - · 0 · - │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · transferOwnership · - · - · - · 0 · - │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · whitelistInvestor · 23450 · 64399 · 50749 · 3 · 0.02 │
···················|······················|·········|··········|·········|················|···················
│ PresaleOracles · whitelistInvestors · 23666 · 119844 · 87785 · 3 · 0.03 │
·------------------|----------------------|---------|----------|---------|----------------|------------------·
16 passing (2m)
23 passing (3m)
```
# Testnet deployment
=====
## Latest Contract deployment
https://kovan.etherscan.io/address/0x6f3f79941f89e03d4af9bdb8be0623dc24f2bef0
=====
## Previous deployments with old source code:
Contract Deployment: https://kovan.etherscan.io/address/0xb9b49e21e77d2d89a9e4c7ef4f684ad2a4e99663#code
Called Initialize by Owner with params:
@ -116,4 +158,3 @@ https://kovan.etherscan.io/address/0xec1afb89f87cb0ac296cad6e73dbeeab5b006050#re
Send 0.1 ether to get rejected:
https://kovan.etherscan.io/tx/0xd859be5b5b58303a4cbc61902f8927efa9de96a3739ce39a18e1f6949a154c2b

View File

@ -18,6 +18,8 @@ contract PresaleOracles is Ownable {
uint256 public totalInvestedInWei;
uint256 public minimumContribution;
mapping(address => uint256) public investorBalances;
mapping(address => bool) public whitelist;
uint256 public investorsLength;
address public vault;
bool public isInitialized = false;
// TESTED by Roman Storm
@ -46,6 +48,7 @@ contract PresaleOracles is Ownable {
}
//TESTED by Roman Storm
function buy() public payable {
require(whitelist[msg.sender]);
require(isValidPurchase(msg.value));
require(isInitialized);
require(getTime() >= startTime && getTime() <= endTime);
@ -81,4 +84,30 @@ contract PresaleOracles is Ownable {
bool withinCap = totalInvestedInWei.add(_amount) <= cap;
return hasMinimumAmount && withinCap && nonZero;
}
//TESTED by Roman Storm
function whitelistInvestor(address _newInvestor) public onlyOwner {
if(!whitelist[_newInvestor]) {
whitelist[_newInvestor] = true;
investorsLength++;
}
}
//TESTED by Roman Storm
function whitelistInvestors(address[] _investors) external onlyOwner {
require(_investors.length <= 250);
for(uint8 i=0; i<_investors.length;i++) {
address newInvestor = _investors[i];
if(!whitelist[newInvestor]) {
whitelist[newInvestor] = true;
investorsLength++;
}
}
}
function blacklistInvestor(address _investor) public onlyOwner {
if(whitelist[_investor]) {
delete whitelist[_investor];
if(investorsLength != 0) {
investorsLength--;
}
}
}
}

View File

@ -115,6 +115,8 @@ contract PresaleOracles is Ownable {
uint256 public totalInvestedInWei;
uint256 public minimumContribution;
mapping(address => uint256) public investorBalances;
mapping(address => bool) public whitelist;
uint256 public investorsLength;
address public vault;
bool public isInitialized = false;
// TESTED by Roman Storm
@ -143,6 +145,7 @@ contract PresaleOracles is Ownable {
}
//TESTED by Roman Storm
function buy() public payable {
require(whitelist[msg.sender]);
require(isValidPurchase(msg.value));
require(isInitialized);
require(getTime() >= startTime && getTime() <= endTime);
@ -178,5 +181,31 @@ contract PresaleOracles is Ownable {
bool withinCap = totalInvestedInWei.add(_amount) <= cap;
return hasMinimumAmount && withinCap && nonZero;
}
//TESTED by Roman Storm
function whitelistInvestor(address _newInvestor) public onlyOwner {
if(!whitelist[_newInvestor]) {
whitelist[_newInvestor] = true;
investorsLength++;
}
}
//TESTED by Roman Storm
function whitelistInvestors(address[] _investors) external onlyOwner {
require(_investors.length <= 250);
for(uint8 i=0; i<_investors.length;i++) {
address newInvestor = _investors[i];
if(!whitelist[newInvestor]) {
whitelist[newInvestor] = true;
investorsLength++;
}
}
}
function blacklistInvestor(address _investor) public onlyOwner {
if(whitelist[_investor]) {
delete whitelist[_investor];
if(investorsLength != 0) {
investorsLength--;
}
}
}
}

4
scripts/.env Normal file
View File

@ -0,0 +1,4 @@
PRESALE_ADDRESS=0x6F3f79941f89E03D4aF9bDb8BE0623DC24F2bef0
UNLOCKED_ADDRESS=0x0039f22efb07a647557c7c5d17854cfd6d489ef3
RPC_PORT=8549
GAS_PRICE=0.7

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
require('dotenv').config();
const ContributionABI = require('../build/contracts/PresaleOracles.json').abi;
// const Web3 = require('web3');
// // const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
// // const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
// const local = `http://localhost:${process.env.RPC_PORT}`;
// const provider = new Web3.providers.HttpProvider(local);
// const web3 = new Web3(provider);
// var myContract = new web3.eth.Contract(ContributionABI, '0x6F3f79941f89E03D4aF9bDb8BE0623DC24F2bef0');
// const ARRAY_OF_ADDRESSES = require('./ARRAY_OF_ADDRESSES.json');
// filterAddresses(ARRAY_OF_ADDRESSES).then(console.log)
function setup({web3Param, contribAddress}){
web3 = web3Param;
CONTRIBUTION_ADDRESS = contribAddress
myContract = new web3.eth.Contract(ContributionABI, CONTRIBUTION_ADDRESS);
}
function readCap() {
myContract.methods.cap().call().then((cap) => {
console.log('cap', cap);
})
}
function isWhitelisted(toCheckAddress) {
readCap();
var count = 0;
var leftRun = toCheckAddress.length;
let notWhitelisted = [];
let promise = new Promise((res) => {
if(toCheckAddress.length === 0 || !toCheckAddress) {
rej('array is empty');
}
console.log(toCheckAddress.length)
toCheckAddress.forEach((address, index) => {
myContract.methods.whitelist(address).call().then((isWhitelisted) => {
leftRun--;
if (!isWhitelisted) {
count++;
notWhitelisted.push(address);
}
if (leftRun === 0) {
console.log('FINISHED! notWhitelisted: ', notWhitelisted.length);
res(notWhitelisted);
}
});
})
})
return promise;
}
function filterAddresses(arrayOfAddresses) {
const date = Date.now();
console.log(date, 'DATE NOW', arrayOfAddresses.length);
return new Promise((res, rej) => {
return isWhitelisted(arrayOfAddresses).then((whitelistedAddress) => {
res(whitelistedAddress);
}).catch(console.error);
})
}
exports.filterAddresses = filterAddresses;
exports.isWhitelisted = isWhitelisted;
exports.setup = setup;

2244
scripts/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

15
scripts/package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "scripts",
"version": "1.0.0",
"description": "",
"main": "whitelist.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Roman Storm",
"license": "MIT",
"dependencies": {
"dotenv": "^4.0.0",
"web3": "^1.0.0-beta.24"
}
}

65
scripts/whitelist.js Normal file
View File

@ -0,0 +1,65 @@
require('dotenv').config();
const ARRAY_OF_ADDRESSES = require('./ARRAY_OF_ADDRESSES.json');
const RPC_PORT = process.env.RPC_PORT;
const PRESALE_ADDRESS = process.env.PRESALE_ADDRESS;
const UNLOCKED_ADDRESS = process.env.UNLOCKED_ADDRESS;
const ICO_ABI = require('../build/contracts/PresaleOracles.json').abi;
const Web3 = require('web3');
const provider = new Web3.providers.HttpProvider(`http://localhost:${RPC_PORT}`);
const web3 = new Web3(provider);
const { filterAddresses, setup } = require('./filterAddresses');
setup({ web3Param: web3, contribAddress: PRESALE_ADDRESS});
filterAddresses(ARRAY_OF_ADDRESSES).then(async (toWhitelist) => {
console.log(toWhitelist.length);
const addPerTx = 160;
const slices = Math.ceil(toWhitelist.length / addPerTx);
var txcount = await web3.eth.getTransactionCount(UNLOCKED_ADDRESS);
const nonce = web3.utils.toHex(txcount);
console.log('STARTED', nonce);
return sendTransactionToContribution({array: toWhitelist, slice: slices, addPerTx, nonce});
}).then(console.log).catch(console.error);
const GAS_PRICE = web3.utils.toWei(process.env.GAS_PRICE, 'gwei');
const GAS_LIMIT = '6700000';
const myContract = new web3.eth.Contract(ICO_ABI, PRESALE_ADDRESS, {
from: UNLOCKED_ADDRESS, // default from address
gasPrice: GAS_PRICE,
gas: GAS_LIMIT // default gas price in wei
});
async function sendTransactionToContribution({array, slice, addPerTx, nonce}) {
if(array.length === 0){
console.log('array doesnot have not whitelisted addresses');
process.exit();
}
const start = (slice - 1) * addPerTx;
const end = slice * addPerTx;
const arrayToProcess = array.slice(start, end);
let encodedData = myContract.methods.whitelistInvestors(arrayToProcess).encodeABI();
console.log('Processing array length', arrayToProcess.length, arrayToProcess[0], arrayToProcess[arrayToProcess.length - 1]);
return new Promise((res) => {
web3.eth.estimateGas({
from: UNLOCKED_ADDRESS, to: PRESALE_ADDRESS, data: encodedData, gas: GAS_LIMIT, gasPrice: GAS_PRICE
}).then((gasNeeded) => {
console.log('gasNeeded', gasNeeded);
web3.eth.sendTransaction({
from: UNLOCKED_ADDRESS, to: PRESALE_ADDRESS, data: encodedData, gas: gasNeeded, gasPrice: GAS_PRICE, nonce
}).on('transactionHash', function(hash){console.log('hash', hash)});
slice--;
if (slice > 0) {
nonce = parseInt(nonce, 16);
nonce++;
nonce = web3.utils.toHex(nonce);
sendTransactionToContribution({array, slice, addPerTx, nonce});
} else {
res({ result: 'completed' });
// process.exit();
}
});
})
}

View File

@ -112,6 +112,11 @@ contract('Presale', function(accounts) {
PRESALE_END_DATE = moment('2017-12-18T16:00:00Z').unix();
await presaleContract.initialize(PRESALE_START_DATE, PRESALE_END_DATE, ETHER.mul(40000), ETHER.mul(100), accounts[1], {from: accounts[0]})
})
it('cannot buy if not whitelisted', async () => {
// require(whitelist[msg.sender]);
await presaleContract.sendTransaction({amount: ETHER})
.should.be.rejectedWith(REVERT_MSG);
})
it('cannot buy if not value is 0', async () => {
// require(msg.value > 0);
await presaleContract.setTime(PRESALE_START_DATE);
@ -150,6 +155,7 @@ contract('Presale', function(accounts) {
const vault = accounts[1];
const preVaultBalance = await web3.eth.getBalance(vault);
await presaleContract.setTime(PRESALE_START_DATE);
await presaleContract.whitelistInvestor(accounts[0]);
await presaleContract.sendTransaction({value: ETHER.mul(100)});
ETHER.mul(100).should.be.bignumber.equal(
await presaleContract.investorBalances(accounts[0])
@ -177,5 +183,78 @@ contract('Presale', function(accounts) {
})
})
describe('whitelisting capabilities', async ()=> {
describe('#whitelistInvestor', async ()=>{
it('cannot by called by non-owner', async ()=> {
await presaleContract.whitelistInvestor(accounts[0], {from: accounts[1]})
.should.be.rejectedWith(REVERT_MSG);
})
it('whitelists an investor', async ()=> {
'0'.should.be.bignumber.equal(
await presaleContract.investorsLength()
)
false.should.be.equal(
await presaleContract.whitelist(accounts[0])
)
await presaleContract.whitelistInvestor(accounts[0]);
true.should.be.equal(
await presaleContract.whitelist(accounts[0])
)
'1'.should.be.bignumber.equal(
await presaleContract.investorsLength()
)
})
})
describe('#whitelistInvestors', async ()=>{
it('cannot by called by non-owner', async ()=> {
await presaleContract.whitelistInvestors([accounts[0]], {from: accounts[1]})
.should.be.rejectedWith(REVERT_MSG);
})
it('whitelists investors', async ()=> {
'0'.should.be.bignumber.equal(
await presaleContract.investorsLength()
)
false.should.be.equal(
await presaleContract.whitelist(accounts[0])
)
await presaleContract.whitelistInvestors([accounts[0], accounts[1], accounts[2]]);
true.should.be.equal(
await presaleContract.whitelist(accounts[2])
)
'3'.should.be.bignumber.equal(
await presaleContract.investorsLength()
)
})
})
describe('#blacklistInvestor', async ()=>{
it('cannot by called by non-owner', async ()=> {
await presaleContract.blacklistInvestor(accounts[0], {from: accounts[1]})
.should.be.rejectedWith(REVERT_MSG);
})
it('blacklist an investors', async ()=> {
'0'.should.be.bignumber.equal(
await presaleContract.investorsLength()
)
false.should.be.equal(
await presaleContract.whitelist(accounts[0])
)
await presaleContract.whitelistInvestors([accounts[0], accounts[1], accounts[2]]);
true.should.be.equal(
await presaleContract.whitelist(accounts[0])
)
'3'.should.be.bignumber.equal(
await presaleContract.investorsLength()
)
await presaleContract.blacklistInvestor(accounts[0]);
false.should.be.equal(
await presaleContract.whitelist(accounts[0])
)
'2'.should.be.bignumber.equal(
await presaleContract.investorsLength()
)
})
})
})
});