@tokenfoundry/sale-contracts
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -7,7 +7,6 @@ module.exports = { | ||
// dir: './secretDirectory', | ||
copyPackages: ['zeppelin-solidity'], | ||
copyPackages: ['zeppelin-solidity', '@tokenfoundry'], | ||
skipFiles: [ | ||
'mocks', | ||
'MultiSigWallet.sol' | ||
'mocks' | ||
] | ||
}; |
# Sale | ||
`Sale.sol` is an abstract sale contract from which child sales, such as Pryze, inherit. `Sale` inherits from `StateMachine` and `Ownable`. | ||
### Sale base contract - Defines the base sale structure with a fixed price | ||
The constructior recieves 4 parameters: | ||
* `address _wallet`: the address of the multisig wallet where funds will be sent once the sale ends. | ||
* `address _userRegistry`: the address of the UserRegistry contract. | ||
* `uint256 _contributionCap`: max amount of ether to raise. | ||
* `uint256 _minThreshold`: minimum amount of ether to raise. | ||
- **Constructor Parameters** | ||
* *address* `_token`: the token address. | ||
* *address* `_disbursementHandler`: the disbursement handler address. | ||
* *uint256* `_contributionCap`: max amount of ether to raise. | ||
* *uint256* `_minContribution`: minimum contribution that an address needs to contribute. | ||
* *uint256* `_minThreshold`: minimum amount of ether to raise. | ||
* *address* `whitelistAdmin`: the admin address that will provide the signatures for the whitelisting. | ||
* *address* `wallet`: the wallet of the project team. | ||
* *uint256* `waitingDuration`: how much time, from the end of the sale, the project team has to deploy their testnet contracts. | ||
* *uint256* `startTime`: the sale starts at this timestamp. | ||
* *uint256* `endTime`: the sale ends at this timestamp (unless the cap is reached before). | ||
## State Machine Stages | ||
`SETUP` | ||
- This contract does **not** have a fallback function. | ||
Configuration stage. The start and end times for the sale need to be set in this stage in order to go to the next one. Also, time locked tokens need to be allocated in this stage by calling the `distributeTimelockedTokens` function. | ||
## Sale Events | ||
Once the configuration is done, the owner needs to call the `setupDone` function, which makes the transition to the next stage after checking the configuration. | ||
### LogContribution(*address* indexed `contributor`, *uint256* `value`, *uint256* `excess`) | ||
`SETUP_DONE` | ||
## Sale Functions | ||
This is just a freeze period in which no more configurations can be done. | ||
### contribute(*uint256* `contributionLimit`, *uint8* `v`, *bytes32* `r`, *bytes32* `s`) public payable | ||
Called by users to contribute to the sale if they have a valid whitelisting signature. Can only be called in the `SALE_IN_PROGRESS` stage. | ||
#### Inputs | ||
| type | name | description | | ||
| --------- | -------- | ---------------- | | ||
| *uint256* | `contributionLimit` | Contribution limit that the user was assigned | | ||
| *uint8* | `v` | `v` component of the ec signature | | ||
| *bytes32* | `r` | `r` component of the ec signature | | ||
| *bytes32* | `s` | `s` component of the ec signature | | ||
### getDisbursements() internal pure | ||
Returns the Disbursements (hardcoded token allocations made at the beginning of the sale). Should be overriden in child sale contracts (no disbursements by default). | ||
#### Outputs | ||
| type | description | | ||
| -------- | -------------------------- | | ||
| *Disbursement[]* | Disbursements to be made at the beginning of the sale | | ||
### startConditions(*bytes32* `stageId`) internal constant | ||
Determines if the given stage should start automatically. This function is overriden from the TimedStateMachine parent contract in order to add the automatic transition to the `SALE_ENDED` stage when the contribution cap is reached. | ||
#### Inputs | ||
| type | name | description | | ||
| --------- | -------- | ---------------- | | ||
| *bytes32* | `stageId` | The id of the stage to query about | | ||
| | ||
#### Outputs | ||
| type | description | | ||
| -------- | -------------------------- | | ||
| *bool* | Should the given stage start? | | ||
### onSaleEnded() internal | ||
Function that is called when entering the `SALE_ENDED` stage. It enables refunds in the Vault contract if the minimum threshold was not reached, and finalizes the token otherwise. | ||
### finalizeToken() internal | ||
Mints the remaining tokens (due to rounding errors) to the owner of the sale, finishes the minting of the token and transfers the ownership of the token to the owner of the sale. | ||
## Sale Stages | ||
`FREEZE` | ||
This is just a freeze period that is active until the sale starts. No allowed functions in this stage (except for ownership related functions). | ||
`SALE_IN_PROGRESS` | ||
This stage has both a start timestamp and an end timestamp. In this stage, whitelisted users (check out the docs for the UserRegistry) can contribute ether calling the `contribute` function. | ||
This stage has both a start timestamp and an end timestamp. In this stage, whitelisted users (check out the docs for the Whitelistable contract) can contribute Ether by calling the `contribute` function, providing a valid whitelist signature and their contribution amount. | ||
@@ -31,2 +88,2 @@ The stage will transition to the next one if the end timestamp is reached or if the contribution cap is reached. | ||
If the sale was successful (minThreshold amount is reached), token allocations can begin. Anyone is able to call the `allocateTokens` function passing as a parameter the address of the contributor. | ||
If the sale fails (minThreshold amount is not reached), the saleFailed function is called in the vault contract to enable refunds. |
{ | ||
"name": "@tokenfoundry/sale-contracts", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "Code for the new token foundry token and sale base contracts.", | ||
@@ -9,3 +9,6 @@ "repository": "https://github.com/tokenfoundry/sale-contracts.git", | ||
"lint": "eslint .", | ||
"compile": "truffle compile", | ||
"coverage": "solidity-coverage", | ||
"coverage-ci": "solidity-coverage && cat coverage/lcov.info | coveralls", | ||
"test": "truffle test", | ||
"generate-wallet": "node ./scripts/generateWallet.js", | ||
@@ -18,4 +21,5 @@ "get-private-key": "node ./scripts/getPrivateKey.js" | ||
"dependencies": { | ||
"@tokenfoundry/state-machine": "^0.1.0", | ||
"zeppelin-solidity": "^1.6.0" | ||
"@tokenfoundry/state-machine": "0.2.4", | ||
"@tokenfoundry/token-contracts": "^0.2.1", | ||
"zeppelin-solidity": "1.7.0" | ||
}, | ||
@@ -30,2 +34,3 @@ "devDependencies": { | ||
"bluebird": "^3.5.1", | ||
"coveralls": "^3.0.0", | ||
"eslint": "^4.14.0", | ||
@@ -44,2 +49,4 @@ "eslint-config-standard": "^11.0.0-beta.0", | ||
"solidity-coverage": "^0.4.7", | ||
"solium": "^1.1.5", | ||
"truffle": "^4.0.6", | ||
"truffle-hdwallet-provider": "0.0.3", | ||
@@ -46,0 +53,0 @@ "web3-provider-engine": "^13.5.0" |
# sale-contracts | ||
[](https://coveralls.io/github/tokenfoundry/sale-contracts) | ||
@@ -7,5 +8,33 @@ Code for the new token foundry token and sale base contracts. | ||
- [Whitelistable.sol](/contracts/Whitelistable.sol): Base contract that implements a signature based whitelisting mechanism. | ||
- [Sale.sol](/contracts/Sale.sol): Abstract base contract from which all sales inherit. It implements a basic sale structure and common functions. | ||
- [DisbursementHandler.sol](/contracts/DisbursementHandler.sol): Contract that is used by the sale in order to lock tokens until a timestamp. | ||
- [DisbursementHandler.sol](/contracts/DisbursementHandler.sol): Contract that is used by the sale to lock tokens until a certain timestamp. | ||
- [Vault.sol](/contracts/Vault.sol): Contract that is used by the sale to store the funds and enable refunds or allow disbursements for the project team. | ||
## Sale Structure | ||
### Overview | ||
We use a simple sale structure where the price of a token is the same for all contributors. The price is calculated as (tokens for sale) / (total wei raised), at the end of the sale. | ||
#### Whitelisting | ||
Instead of storing in the contract the users that are allowed to contribute, we use a signature based whitelisting. | ||
After the user goes through an _off-chain_ KYC process, we compute the keccak256 hash of the contributor's address concatenated with their contribution limit, and sign that hash using the whitelisting admin's private key. The user will have to contribute from the provided address, and will have to pass as argument to the contribute function the contribution limit and the signature. | ||
#### Refunds | ||
All funds are immediatly sent to the `Vault` contract (the Sale contract doesn't store funds). Those funds are locked, and if at the end of the sale the minimum threshold is **not** reached, 100% of the funds are available for refunds. If at some point in the sale the minimum thresold is reached, a percentage of the funds is sent to the project team's wallet, and the rest is kept (locked) in the vault until the team publishes their contracts on testnet. | ||
The `Vault` contract should be owned by a multisig wallet, as the owner is able to force refunds at any moment. | ||
### State | ||
 | ||
### Flow | ||
TODO | ||
## Instructions | ||
@@ -17,11 +46,11 @@ | ||
Once the repo is cloned, run `npm install` to install all the dependencies. | ||
Once the repo is cloned, run `yarn install` to install all the dependencies. | ||
Running `truffle compile` will compile the contract code and place the output in the `build/contracts` directory. | ||
Running `yarn run compile` will compile the contract code and place the output in the `build/contracts` directory. | ||
### Testing | ||
`truffle test` to run all the tests. | ||
`yarn run test` to run all the tests, or `yarn run test test/<test file>` to run a specific test. | ||
`npm run coverage` to run tests and get code coverage. | ||
`yarn run coverage` to run tests and get code coverage. | ||
@@ -28,0 +57,0 @@ ## License |
564
test/sale.js
@@ -5,138 +5,164 @@ import increaseTime, { duration } from './helpers/increaseTime'; | ||
import PromisifyWeb3 from './helpers/promisifyWeb3'; | ||
import { bufferToHex, fromRpcSig } from 'ethereumjs-util'; | ||
PromisifyWeb3.promisify(web3); | ||
const SaleMock = artifacts.require('SaleMock.sol'); | ||
const Sale = artifacts.require('Sale.sol'); | ||
const DisbursementHandler = artifacts.require('DisbursementHandler'); | ||
const MintableToken = artifacts.require('MintableToken'); | ||
const Token = artifacts.require('Token'); | ||
const Vault = artifacts.require('Vault'); | ||
const contributionHash = (address, limit) => { | ||
return web3.sha3(address + web3.padLeft(web3.toHex(limit).slice(2), 64), { encoding: 'hex' }); | ||
}; | ||
contract('Sale', (accounts) => { | ||
const owner = accounts[0]; | ||
const nonOwner = accounts[1]; | ||
const whitelistAdmin = accounts[2]; | ||
const whitelistedAddress = accounts[3]; | ||
const nonWhitelistedAddress = accounts[4]; | ||
const beneficiary = accounts[5]; | ||
const wallet = accounts[6]; | ||
const notController = accounts[7]; | ||
const nonParticipant = accounts[8]; | ||
const contributionCap = new web3.BigNumber(10000); | ||
const tokensPerWei = new web3.BigNumber(10); | ||
const contributionLimit = new web3.BigNumber(100); | ||
const whitelistedAddress = accounts[1]; | ||
const beneficiary = accounts[3]; | ||
let sale; | ||
let token; | ||
let disbursementHandler; | ||
let vault; | ||
// Go back to the SALE_IN_PROGRESS stage and reset contributions | ||
const resetSaleEnded = async () => { | ||
await sale.goToStage('setup'); | ||
await sale.removeContribution(whitelistedAddress); | ||
await sale.throwFundsAway(); | ||
const latest = await latestTime(); | ||
await sale.setSaleEndTime(latest + duration.weeks(1)); | ||
await sale.goToStage('saleInProgress'); | ||
}; | ||
let contributionCap; | ||
let minContribution; | ||
let minThreshold; | ||
let maxTokens; | ||
let waitingDuration; | ||
before(async () => { | ||
sale = await SaleMock.new( | ||
accounts[9], | ||
contributionCap, | ||
tokensPerWei, | ||
contributionLimit | ||
); | ||
let contributionLimit; | ||
let contributionLimitBig; | ||
let hash; | ||
let hash2; | ||
let sig; | ||
let sig2; | ||
const tokenAddress = await sale.token.call(); | ||
token = await MintableToken.at(tokenAddress); | ||
let startTime; | ||
let endTime; | ||
const disbursementHandlerAddress = await sale.disbursementHandler.call(); | ||
disbursementHandler = await DisbursementHandler.at(disbursementHandlerAddress); | ||
}); | ||
beforeEach(async () => { | ||
await sale.setContributionLimit(contributionLimit); | ||
}); | ||
contributionCap = new web3.BigNumber(10000); | ||
minContribution = new web3.BigNumber(1); | ||
minThreshold = new web3.BigNumber(2000); | ||
maxTokens = new web3.BigNumber(100000000000); | ||
waitingDuration = new web3.BigNumber(duration.weeks(2)); | ||
describe('SETUP stage', async () => { | ||
it('should begin in the SETUP stage', async () => { | ||
const stageId = web3.toUtf8(await sale.getCurrentStageId.call()); | ||
assert.equal(stageId, 'setup'); | ||
}); | ||
contributionLimit = new web3.BigNumber(5000); | ||
contributionLimitBig = contributionCap.add(1000); | ||
hash = contributionHash(whitelistedAddress, contributionLimit); | ||
hash2 = contributionHash(whitelistedAddress, contributionLimitBig); | ||
sig = fromRpcSig(web3.eth.sign(whitelistAdmin, hash)); | ||
sig2 = fromRpcSig(web3.eth.sign(whitelistAdmin, hash2)); | ||
it('should not be possible to call non-allowed functions', async () => { | ||
await expectThrow(sale.contribute({from: whitelistedAddress, value: 1})); | ||
await expectThrow(sale.allocateTokens(whitelistedAddress)); | ||
await expectThrow(sale.transferOwnership(accounts[1] ,{ from: accounts[1] })); | ||
}); | ||
await increaseTime(duration.weeks(1)); | ||
startTime = (await latestTime()) + duration.weeks(1); | ||
endTime = startTime + duration.weeks(1); | ||
it('should not be possible to contribute by sending ether (fallback function)', async () => { | ||
await expectThrow(sale.sendTransaction({ from: whitelistedAddress, value: 1 })); | ||
}); | ||
// token = await MintableToken.new({ from: owner }); | ||
// disbursementHandler = await DisbursementHandler.new(token.address, { from: owner }); | ||
it('should not be possible to call setupDone function if the configuration is not complete', async () => { | ||
await expectThrow(sale.setupDone()); | ||
}); | ||
sale = await Sale.new( | ||
// token.address, | ||
// disbursementHandler.address, | ||
contributionCap, | ||
minContribution, | ||
minThreshold, | ||
maxTokens, | ||
whitelistAdmin, | ||
wallet, | ||
waitingDuration, | ||
startTime, | ||
{ from: owner } | ||
); | ||
it('should not be possible to call setupDone function if startTime > endTime', async () => { | ||
let startTime = (await latestTime()) + duration.weeks(1); | ||
await sale.setSaleStartTime(startTime); | ||
let _startTime = await sale.getStageStartTime.call('saleInProgress'); | ||
assert.equal(_startTime.equals(startTime), true); | ||
const tokenAddress = await sale.trustedToken.call(); | ||
token = await Token.at(tokenAddress); | ||
await expectThrow(sale.setupDone()); | ||
}); | ||
const disbursementHandlerAddress = await sale.disbursementHandler.call(); | ||
disbursementHandler = await DisbursementHandler.at(disbursementHandlerAddress); | ||
it('should be possible to distribute time locked tokens', async () => { | ||
const oldBalance = await token.balanceOf.call(disbursementHandler.address); | ||
assert.equal(oldBalance.toNumber(), 0); | ||
const vaultAddress = await sale.trustedVault.call(); | ||
vault = await Vault.at(vaultAddress); | ||
const amount = 100; | ||
const timestamp = (await latestTime()) + duration.weeks(1); | ||
await sale.distributeTimelockedTokens(beneficiary, amount, timestamp); | ||
}); | ||
const newBalance = await token.balanceOf.call(disbursementHandler.address); | ||
assert.equal(newBalance.toNumber(), amount); | ||
describe('Check initial values', async () => { | ||
it('should create Sale with correct initial values', async () => { | ||
const saleContributionCap = await sale.contributionCap.call(); | ||
assert.isTrue(saleContributionCap.equals(contributionCap), 'ContributionCap value isn\'t correct'); | ||
const disbursement = await disbursementHandler.disbursements.call(beneficiary, 0); | ||
assert.equal(disbursement[0].equals(timestamp), true); | ||
assert.equal(disbursement[1].equals(amount), true); | ||
}); | ||
const saleMinContribution = await sale.minContribution.call(); | ||
assert.isTrue(saleMinContribution.equals(minContribution), 'MinContribution value isn\'t correct'); | ||
it('should go to SETUP_DONE stage when setupDone function is called and configuration is complete', async () => { | ||
let stageId; | ||
stageId = await sale.getCurrentStageId.call(); | ||
assert.equal(web3.toUtf8(stageId), 'setup'); | ||
const saleMinThreshold = await sale.minThreshold.call(); | ||
assert.isTrue(saleMinThreshold.equals(minThreshold), 'MinThreshold value isn\'t correct'); | ||
let startTime = (await latestTime()) + duration.weeks(1); | ||
let endTime = startTime + duration.weeks(1); | ||
const saleWhitelistAdminAddress = await sale.whitelistAdmin.call(); | ||
assert.equal(saleWhitelistAdminAddress, whitelistAdmin , 'Whitelist Admin address isn\'t correct'); | ||
await sale.setSaleStartTime(startTime); | ||
let _startTime = await sale.getStageStartTime.call('saleInProgress'); | ||
assert.equal(_startTime.equals(startTime), true); | ||
const saleWalletAddress = await vault.trustedWallet.call(); | ||
assert.equal(saleWalletAddress, wallet , 'Vault wallet addres isn\'t correct'); | ||
await sale.setSaleEndTime(endTime); | ||
let _endTime = await sale.getStageStartTime.call('saleEnded'); | ||
assert.equal(_endTime.equals(endTime), true); | ||
const saleWaitingDuration = await vault.closingDuration.call(); | ||
assert.isTrue(saleWaitingDuration.equals(waitingDuration), 'Vault closing time isn\'t correct'); | ||
const saleStartTime = await sale.getStageStartTime( 'saleInProgress' , { from: beneficiary }); | ||
assert.isTrue(saleStartTime.equals(startTime), 'Sale in progress start time isn\'t correct'); | ||
await sale.setupDone(); | ||
stageId = await sale.getCurrentStageId.call(); | ||
assert.equal(web3.toUtf8(stageId), 'setupDone'); | ||
}); | ||
}); | ||
describe('SETUP_DONE stage', async () => { | ||
describe('FREEZE stage', async () => { | ||
it('should not be possible to call non-allowed functions', async () => { | ||
await expectThrow(sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: 10 } | ||
)); | ||
await expectThrow(sale.transferOwnership(accounts[1] ,{ from: accounts[1] })); | ||
await expectThrow(sale.changeAdmin(nonWhitelistedAddress, { from: whitelistedAddress })); | ||
await expectThrow(sale.allocateExtraTokens(whitelistedAddress)); | ||
const latest = await latestTime(); | ||
await expectThrow(sale.setSaleStartTime(latest + duration.weeks(1))); | ||
await expectThrow(sale.setSaleEndTime(latest + duration.weeks(2))); | ||
await expectThrow(sale.setupDone()); | ||
await expectThrow(sale.contribute({from: accounts[0], value: 1})); | ||
await expectThrow(sale.allocateTokens(whitelistedAddress)); | ||
await expectThrow(sale.transferOwnership(accounts[1] ,{ from: accounts[1] })); | ||
await expectThrow(sale.setEndTime(latest + duration.weeks(1), { from: whitelistedAddress } )); | ||
await expectThrow(sale.setEndTime(latest + duration.weeks(1), { from: owner } )); | ||
}); | ||
it('should not be possible to change the token controller if you aren\'t the sale)', async () => { | ||
await expectThrow(token.setController(notController, { from: nonOwner})); | ||
}); | ||
it('should not be possible to contribute by sending ether (fallback function)', async () => { | ||
await expectThrow(sale.sendTransaction({ from: whitelistedAddress, value: 1 })); | ||
await expectThrow(sale.sendTransaction({ from: whitelistedAddress, value: 10 })); | ||
}); | ||
it('should be possible to change whitelist admin', async () => { | ||
const actualAdmin = await sale.whitelistAdmin.call(); | ||
assert.equal(actualAdmin, whitelistAdmin, 'whitelistAdmin isn\'t whitelist admin'); | ||
await sale.changeAdmin(nonWhitelistedAddress, { from: whitelistAdmin }); | ||
let newAdmin = await sale.whitelistAdmin.call(); | ||
assert.equal(newAdmin, nonWhitelistedAddress, 'Whitelist admin wasn\'t transfered'); | ||
// Transfer ownership back to initial owner | ||
await sale.changeAdmin(whitelistAdmin, { from: newAdmin }); | ||
newAdmin = await sale.whitelistAdmin.call(); | ||
assert.equal(newAdmin, whitelistAdmin, 'Ownership isnt back to initial admin'); | ||
}); | ||
it('should go to SALE_IN_PROGRESS stage when a week has passed', async () => { | ||
let stageId = await sale.getCurrentStageId.call(); | ||
assert.equal(web3.toUtf8(stageId), 'setupDone'); | ||
assert.equal(web3.toUtf8(stageId), 'freeze'); | ||
@@ -153,88 +179,132 @@ await increaseTime(duration.weeks(1.1)); | ||
beforeEach(async () => { | ||
await resetSaleEnded(); | ||
await increaseTime(duration.weeks(1.1)); | ||
await sale.conditionalTransitions(); | ||
}); | ||
it('should not be possible to call non-allowed functions', async () => { | ||
const latest = await latestTime(); | ||
await expectThrow(sale.setSaleStartTime(latest + duration.weeks(1))); | ||
await expectThrow(sale.setSaleEndTime(latest + duration.weeks(2))); | ||
await expectThrow(sale.setupDone()); | ||
await expectThrow(sale.allocateTokens(whitelistedAddress)); | ||
await expectThrow(sale.transferOwnership(accounts[1], { from: nonOwner })); | ||
await expectThrow(sale.changeAdmin(nonWhitelistedAddress, { from: whitelistedAddress })); | ||
await expectThrow(sale.allocateExtraTokens(whitelistedAddress)); | ||
}); | ||
await expectThrow(sale.transferOwnership(accounts[1] ,{ from: accounts[1] })); | ||
it('should not be possible to change the token controller if you aren\'t the sale', async () => { | ||
await expectThrow(token.setController(notController, { from: nonOwner})); | ||
}); | ||
it('should not be possible to contribute with value == 0', async () => { | ||
await expectThrow(sale.contribute({from: whitelistedAddress, value: 0})); | ||
it('should allow a transfer from the sale', async () => { | ||
const isTransferAllowed = await sale.transferAllowed.call(sale.address, beneficiary); | ||
assert.isTrue(isTransferAllowed); | ||
}); | ||
it('should be possible to contribute with excess and get the excess refunded', async () => { | ||
const userTotalContributions = await sale.contributions.call(whitelistedAddress); | ||
const excess = 23; | ||
it('should not allow a transfer from an account that is not the sale', async () => { | ||
const isTransferAllowed = await sale.transferAllowed.call(nonOwner, beneficiary); | ||
assert.isFalse(isTransferAllowed); | ||
}); | ||
it('should not be possible to contribute with value < minContribution', async () => { | ||
await expectThrow(sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: minContribution.sub(1) } | ||
)); | ||
}); | ||
let contribution = contributionLimit - userTotalContributions ; | ||
contribution = contribution + excess; | ||
it('should not be possible to contribute if the signature doesn\'t match the sender', async () => { | ||
await expectThrow(sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: nonWhitelistedAddress, value: 1 } | ||
)); | ||
}); | ||
const result = await sale.contribute({from: whitelistedAddress, value: contribution}); | ||
it('should be possible to contribute with minContribution < value < contributionLimit', async () => { | ||
let contribution = Math.round((contributionLimit.add(minContribution)) / 2); | ||
const { amountSent, excessRefunded } = result.logs[0].args; | ||
const result = await sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
assert.equal(amountSent, contribution, 'Amount sent is wrong.'); | ||
assert.equal(excessRefunded, excess, 'Excess value is not correct.'); | ||
const { value, excess } = result.logs[0].args; | ||
assert.isTrue(value.equals(contribution), 'Amount sent is wrong.'); | ||
assert.isTrue(excess.equals(0), 'Excess value is not correct.'); | ||
}); | ||
it('should be possible to contribute by sending ether (fallback function)', async () => { | ||
await sale.sendTransaction({ value: 1, from: whitelistedAddress }); | ||
const contributed = await sale.contributions.call(whitelistedAddress); | ||
assert.equal(contributed, 1); | ||
it('should not be possible to allocate extra tokens during the sale, even to a contributor', async () => { | ||
await expectThrow(sale.allocateExtraTokens(nonParticipant, { from: whitelistedAddress })); | ||
//someone contributes | ||
let contribution = Math.round((contributionLimit.add(minContribution)) / 2); | ||
const result = await sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
await expectThrow(sale.allocateExtraTokens(whitelistedAddress, { from: whitelistedAddress })); | ||
}); | ||
it('should be possible contribute with a value that is over contributionCap and get refunded with the excess.', async () => { | ||
await sale.setContributionLimit(contributionCap.plus(1000)); | ||
const initial_contribution = contributionCap.minus(900); | ||
await sale.contribute({from: whitelistedAddress, value: initial_contribution}); | ||
it('should be possible to contribute with excess and get the excess refunded', async () => { | ||
const _excess = 23; | ||
const excess = 100; | ||
let contribution = 900 + excess; | ||
let contribution = contributionLimit.add(_excess); | ||
let result = await sale.contribute({from: whitelistedAddress, value: contribution}); | ||
const result = await sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
let { amountSent, excessRefunded } = result.logs[0].args; | ||
const { value, excess } = result.logs[0].args; | ||
const weiContributed = await sale.weiContributed.call(); | ||
assert.isTrue(value.equals(contribution.minus(_excess)), 'Amount sent is wrong.'); | ||
assert.isTrue(excess.equals(_excess), 'Excess value is not correct.'); | ||
}); | ||
assert.equal(amountSent.equals(contribution), true, 'Amount sent is wrong.'); | ||
assert.equal(excessRefunded.equals(excess), true, 'Excess value is wrong.'); | ||
assert.equal(weiContributed.equals(contributionCap), true, 'weiContributed is not equal to contributionCap.'); | ||
it('should not be possible to contribute by sending ether (fallback function)', async () => { | ||
await expectThrow(sale.sendTransaction({ value: 10, from: whitelistedAddress })); | ||
}); | ||
it('should be possible contribute with a value greater than contribution cap and ' + | ||
'after calculating the excess, total contribution is greater than contribution limit '+ | ||
'and excess should be refunded.', async () => { | ||
const newContributionLimit = contributionCap.minus(800); | ||
await sale.setContributionLimit(newContributionLimit); | ||
it('should be possible to set the end time for the sale', async () => { | ||
const saleEndTime = startTime + duration.weeks(4.5); | ||
await sale.setEndTime(saleEndTime); | ||
const contribution = contributionCap.add(100); | ||
const excess = contribution.minus(newContributionLimit).plus(1); | ||
const actualEndTime = await sale.getStageStartTime.call('saleEnded'); | ||
await sale.contribute({from: whitelistedAddress, value: 1}); | ||
let result = await sale.contribute({from: whitelistedAddress, value: contribution}); | ||
assert.isTrue(actualEndTime.equals(saleEndTime), 'End time was not correctly set'); | ||
}); | ||
let { amountSent, excessRefunded } = result.logs[0].args; | ||
assert.equal(amountSent.equals(contribution), true, 'Amount sent is wrong.'); | ||
assert.equal(excessRefunded.equals(excess), true, 'Excess value is wrong.'); | ||
it('should not be possible to set the end time for the sale in the past', async () => { | ||
const pastEndTime = await latestTime(); | ||
await expectThrow(sale.setEndTime(pastEndTime)); | ||
}); | ||
let weiContributed = await sale.weiContributed.call(); | ||
assert.equal(weiContributed.toNumber(), newContributionLimit.toNumber(), 'weiContributed is not equal to contributionCap.'); | ||
it('should not be possible to set the end time if one has already been set', async () => { | ||
const saleEndTime = startTime + duration.weeks(4.5); | ||
await sale.setEndTime(saleEndTime); | ||
// the end time has now been set | ||
let userTotalContributions = await sale.contributions.call(whitelistedAddress); | ||
assert.equal(userTotalContributions.toNumber(), newContributionLimit.toNumber(), 'User contributions are not equal to user contribution limit.'); | ||
await increaseTime(duration.weeks(1.1)); | ||
await sale.conditionalTransitions(); | ||
const newSaleEndTime = saleEndTime + duration.weeks(1); | ||
await expectThrow(sale.setEndTime(newSaleEndTime)); | ||
}); | ||
it ('should not got to SALE_ENDED stage if the cap is not reached or a week has not passed', async () => { | ||
it ('should not go to SALE_ENDED stage if the cap is not reached or if the end timestamp not passed', async () => { | ||
let stageId = await sale.getCurrentStageId.call(); | ||
assert.equal(web3.toUtf8(stageId), 'saleInProgress'); | ||
const latest = await latestTime(); | ||
await sale.setEndTime(latest + duration.weeks(1)); | ||
await expectThrow(sale.setEndTime(latest + duration.weeks(1))); // Should throw if called 2nd time | ||
await sale.conditionalTransitions(); | ||
@@ -246,12 +316,45 @@ | ||
it('should be possible to contribute with a value that is over contributionCap and get refunded with the excess.', async () => { | ||
const initial_contribution = contributionCap.minus(900); | ||
await sale.contribute( | ||
contributionLimitBig, | ||
sig2.v, | ||
bufferToHex(sig2.r), | ||
bufferToHex(sig2.s), | ||
{ from: whitelistedAddress, value: initial_contribution } | ||
); | ||
const _excess = 100; | ||
let contribution = 900 + _excess; | ||
let result = await sale.contribute( | ||
contributionLimitBig, | ||
sig2.v, | ||
bufferToHex(sig2.r), | ||
bufferToHex(sig2.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
let { value, excess } = result.logs[0].args; | ||
const weiContributed = await sale.weiContributed.call(); | ||
assert.isTrue(value.equals(contribution - _excess), 'Amount sent is wrong.'); | ||
assert.isTrue(excess.equals(_excess), 'Excess value is wrong.'); | ||
assert.isTrue(weiContributed.equals(contributionCap), 'weiContributed is not equal to contributionCap.'); | ||
}); | ||
it('should go to SALE_ENDED stage when the cap is reached', async () => { | ||
await sale.setContributionLimit(contributionCap); | ||
let stageId = await sale.getCurrentStageId.call(); | ||
assert.equal(web3.toUtf8(stageId), 'saleInProgress'); | ||
let weiContributed = await sale.weiContributed.call(); | ||
const remaining = contributionCap.minus(weiContributed); | ||
await sale.contribute({ from: whitelistedAddress, value: remaining }); | ||
await sale.contribute( | ||
contributionLimitBig, | ||
sig2.v, | ||
bufferToHex(sig2.r), | ||
bufferToHex(sig2.s), | ||
{ from: whitelistedAddress, value: contributionCap.add(100) } | ||
); | ||
weiContributed = await sale.weiContributed.call(); | ||
const weiContributed = await sale.weiContributed.call(); | ||
assert.isTrue(weiContributed.equals(contributionCap), 'Cap wasn\'t reached'); | ||
@@ -265,6 +368,12 @@ | ||
it('should go to SALE_ENDED stage when a week has passed', async () => { | ||
it('should go to SALE_ENDED stage when the end time has passed', async () => { | ||
let stageId = await sale.getCurrentStageId.call(); | ||
assert.equal(web3.toUtf8(stageId), 'saleInProgress'); | ||
const latest = await latestTime(); | ||
// Should throw if called not by the owner | ||
await expectThrow(sale.setEndTime(latest + duration.weeks(1), { from: whitelistedAddress })); | ||
await sale.setEndTime(latest + duration.weeks(1) , { from: owner }); | ||
await increaseTime(duration.weeks(1.1)); | ||
@@ -280,13 +389,39 @@ await sale.conditionalTransitions(); | ||
describe('SALE_ENDED stage', async () => { | ||
beforeEach(async () => { | ||
await increaseTime(duration.weeks(1.1)); | ||
await sale.conditionalTransitions(); | ||
const latest = await latestTime(); | ||
endTime = latest + duration.weeks(1); | ||
await sale.setEndTime(endTime); | ||
}); | ||
it('should not be possible to call non-allowed functions', async () => { | ||
const latest = await latestTime(); | ||
await expectThrow(sale.setSaleStartTime(latest + duration.weeks(1))); | ||
await expectThrow(sale.setSaleEndTime(latest + duration.weeks(2))); | ||
await expectThrow(sale.setupDone()); | ||
await expectThrow(sale.contribute({ from: whitelistedAddress, value: new web3.BigNumber(1) })); | ||
await expectThrow(sale.allocateTokens(whitelistedAddress)); | ||
//reach the sale end | ||
await increaseTime(endTime - startTime + duration.hours(1)); | ||
await sale.conditionalTransitions(); | ||
await expectThrow(sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: new web3.BigNumber(1) } | ||
)); | ||
await expectThrow(sale.transferOwnership(accounts[1] ,{ from: accounts[1] })); | ||
await expectThrow(sale.changeAdmin(nonWhitelistedAddress, { from: whitelistedAddress })); | ||
const newSaleEndTime = endTime + duration.weeks(1); | ||
await expectThrow(sale.setEndTime(newSaleEndTime)); | ||
}); | ||
it('should not be possible to change the token controller if you aren\'t the sale)', async () => { | ||
await expectThrow(token.setController(notController, { from: nonOwner})); | ||
}); | ||
it('should not be possible to contribute by sending ether (fallback function)', async () => { | ||
await increaseTime(endTime - startTime + duration.hours(1)); | ||
await sale.conditionalTransitions(); | ||
await expectThrow(sale.sendTransaction({ value: 1, from: whitelistedAddress })); | ||
@@ -296,18 +431,56 @@ }); | ||
it('should have called the onSaleEnded callback', async () => { | ||
const saleEnded = await sale.saleEnded.call(); | ||
assert.equal(saleEnded, true); | ||
await increaseTime(endTime - startTime + duration.hours(1)); | ||
await sale.conditionalTransitions(); | ||
const state = await vault.state.call(); | ||
assert.isTrue(state.equals(2)); // Refunding state | ||
}); | ||
it('should enable refunds if the threshold was not reached', async () => { | ||
const contribution = minThreshold.minus(10); | ||
await sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
await increaseTime(endTime - startTime + duration.hours(1)); | ||
await sale.conditionalTransitions(); | ||
const state = await vault.state.call(); | ||
assert.isTrue(state.equals(2)); | ||
}); | ||
it('should set the vault state as success if the threshold was reached', async () => { | ||
const contribution = minThreshold.plus(10); | ||
await sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
const state = await vault.state.call(); | ||
assert.isTrue(state.equals(1)); | ||
}); | ||
it('should be possible to allocate tokens when the sale ends', async () => { | ||
// Reset contributions and go to SALE_IN_PROGRESS stage | ||
await resetSaleEnded(); | ||
const contribution = new web3.BigNumber(4111); | ||
await sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
const contribution = new web3.BigNumber(5000); | ||
await sale.setContributionLimit(contribution); | ||
await sale.contribute({ from: whitelistedAddress, value: contribution }); | ||
const tokensPerWei = await sale.tokensPerWei.call(); | ||
const weiContributed = await sale.weiContributed.call(); | ||
assert.equal(weiContributed.equals(contribution), true); | ||
// Go to SALE_ENDED stage again | ||
// Go to SALE_ENDED stage | ||
await increaseTime(duration.weeks(1.1)); | ||
@@ -321,11 +494,60 @@ await sale.conditionalTransitions(); | ||
const beforeAllocate = await token.balanceOf.call(whitelistedAddress); | ||
assert.equal(beforeAllocate.equals(0), true); | ||
assert.isTrue(beforeAllocate.equals(contribution.mul(maxTokens).div(contributionCap).floor())); | ||
await sale.allocateTokens(whitelistedAddress); | ||
const newTokensPerWei = await sale.tokensPerWei.call(); | ||
// Check that (contribution * tokensPerWei) tokens were allocated | ||
assert.isTrue(newTokensPerWei.equals(tokensPerWei.times(contributionCap).div(weiContributed).floor())); | ||
await sale.allocateExtraTokens(whitelistedAddress); | ||
const afterAllocate = await token.balanceOf.call(whitelistedAddress); | ||
assert.equal(afterAllocate.equals(contribution.mul(tokensPerWei)), true, 'Tokens were not allocated'); | ||
assert.isTrue(afterAllocate.equals(contribution.times(newTokensPerWei)), 'Tokens were not allocated'); | ||
// Calling allocateExtraTokens 2nd time must fail | ||
await expectThrow(sale.allocateExtraTokens(whitelistedAddress)); | ||
}); | ||
it('should not be possible to allocate extra tokens to someone who has not contributed', async () => { | ||
//ensure the sale succeeds | ||
const contribution = minThreshold.plus(10); | ||
await sale.contribute( | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s), | ||
{ from: whitelistedAddress, value: contribution } | ||
); | ||
// Go to SALE_ENDED stage | ||
await increaseTime(duration.weeks(1.1)); | ||
await sale.conditionalTransitions(); | ||
//check that the address didnt contribute | ||
const beforeAllocate = await token.balanceOf.call(nonParticipant); | ||
assert.isTrue(beforeAllocate.equals(0)); | ||
//try to allocate extra tokens to them | ||
await expectThrow(sale.allocateExtraTokens(nonParticipant)); | ||
}); | ||
it('should transfer the ownership of the token to the sale owner', async() => { | ||
await increaseTime(endTime - startTime + duration.hours(1)); | ||
await sale.conditionalTransitions(); | ||
const tokenOwner = await token.owner.call(); | ||
assert.equal(tokenOwner, owner); | ||
}); | ||
it('should transfer the ownership of the vault to the sale owner', async() => { | ||
await increaseTime(endTime - startTime + duration.hours(1)); | ||
await sale.conditionalTransitions(); | ||
const vaultOwner = await vault.owner.call(); | ||
assert.equal(vaultOwner, owner); | ||
}); | ||
}); | ||
}); |
import expectThrow from './helpers/expectThrow'; | ||
import { bufferToHex, fromRpcSig } from 'ethereumjs-util'; | ||
const Whitelistable = artifacts.require('Whitelistable.sol'); | ||
const contributionHash = (address, limit) => { | ||
return web3.sha3(address + web3.padLeft(web3.toHex(limit).slice(2), 64), { encoding: 'hex' }); | ||
}; | ||
contract('Whitelistable', (accounts) => { | ||
let whitelistable; | ||
const owner = accounts[0]; | ||
const user = accounts[1]; | ||
const admin = accounts[0]; | ||
const nonAdmin = accounts[1]; | ||
const user = accounts[2]; | ||
const zeroAddress = web3.toHex(0); | ||
beforeEach(async () => { | ||
whitelistable = await Whitelistable.new(); | ||
whitelistable = await Whitelistable.new(admin); | ||
}); | ||
it('should be owned by owner', async () => { | ||
const _owner = await whitelistable.owner.call(); | ||
assert.equal(_owner, owner, 'Owner is wrong'); | ||
it('should set the admin in the constructor', async () => { | ||
const _admin = await whitelistable.whitelistAdmin.call(); | ||
assert.equal(_admin, admin, 'Admin is wrong'); | ||
}); | ||
it('should not be possible for other user to change the owner', async () => { | ||
await expectThrow(whitelistable.transferOwnership(user ,{ from: user })); | ||
it('should not be possible for other user to change the admin', async () => { | ||
await expectThrow(whitelistable.changeAdmin(user ,{ from: user })); | ||
}); | ||
it('should be possible to transfer ownership', async () => { | ||
await whitelistable.transferOwnership(user ,{ from: owner }); | ||
it('should be possible to change the admin', async () => { | ||
await whitelistable.changeAdmin(user, { from: admin }); | ||
const new_owner = await whitelistable.owner.call(); | ||
assert.equal(new_owner, user, 'Ownership wasn t transfered'); | ||
let newAdmin = await whitelistable.whitelistAdmin.call(); | ||
assert.equal(newAdmin, user, 'Ownership wasn\'t transfered'); | ||
//trasnfer ownership back to initial owner | ||
await whitelistable.transferOwnership(owner ,{ from: user }); | ||
//transfer ownership back to initial owner | ||
await whitelistable.changeAdmin(admin, { from: user }); | ||
const _owner = await whitelistable.owner.call(); | ||
assert.equal(_owner, owner, 'Ownership isnt back to initial owner'); | ||
newAdmin = await whitelistable.whitelistAdmin.call(); | ||
assert.equal(newAdmin, admin, 'Ownership isnt back to initial admin'); | ||
}); | ||
it('should not be possible to register a user with zero address', async () => { | ||
await expectThrow(whitelistable.registerUser(zeroAddress)); | ||
const isWhitelisted = await whitelistable.whitelisted.call(zeroAddress); | ||
assert.isFalse(isWhitelisted); | ||
it('should not return true in the checkWhitelisted function if the signature is wrong', async () => { | ||
const contributionLimit = web3.toWei(5); | ||
const hash = contributionHash(user, contributionLimit); | ||
const sig = fromRpcSig(web3.eth.sign(nonAdmin, hash)); | ||
const whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
}); | ||
it('should be possible to register a user with non-zero address', async () => { | ||
await whitelistable.registerUser(user); | ||
const isWhitelisted = await whitelistable.whitelisted.call(user); | ||
assert.isTrue(isWhitelisted); | ||
it('should return true in the checkWhitelisted function if the signature is OK and the contributor isnt blacklisted', async () => { | ||
const contributionLimit = web3.toWei(5); | ||
const hash = contributionHash(user, contributionLimit); | ||
const sig = fromRpcSig(web3.eth.sign(admin, hash)); | ||
const whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isTrue(whitelisted); | ||
}); | ||
it('should return false in the checkWhitelisted function if the signature is OK but the contributor is blacklisted', async () => { | ||
const contributionLimit = web3.toWei(5); | ||
const hash = contributionHash(user, contributionLimit); | ||
const sig = fromRpcSig(web3.eth.sign(admin, hash)); | ||
it('should be possible to register a user with non-zero address more than one time', async () => { | ||
await whitelistable.registerUser(user); | ||
const isWhitelisted_1 = await whitelistable.whitelisted.call(user); | ||
assert.isTrue(isWhitelisted_1); | ||
await whitelistable.addToBlacklist(user, { from: admin }); | ||
await whitelistable.registerUser(user); | ||
const isWhitelisted_2 = await whitelistable.whitelisted.call(user); | ||
assert.isTrue(isWhitelisted_2); | ||
const whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
}); | ||
await whitelistable.registerUser(user); | ||
const isWhitelisted_3 = await whitelistable.whitelisted.call(user); | ||
assert.isTrue(isWhitelisted_3); | ||
}); | ||
it('should return true in the checkWhitelisted function if the contributor is removed from the blacklist', async () => { | ||
const contributionLimit = web3.toWei(5); | ||
const hash = contributionHash(user, contributionLimit); | ||
const sig = fromRpcSig(web3.eth.sign(admin, hash)); | ||
it('should not be possible to unregister an unregistered user', async () => { | ||
await expectThrow(whitelistable.unregisterUser(user)); | ||
}); | ||
await whitelistable.addToBlacklist(user, { from: admin }); | ||
await whitelistable.removeFromBlacklist(user, { from: admin }); | ||
it('should be possible to unregister a registered user', async () => { | ||
await whitelistable.registerUser(user); | ||
let isWhitelisted = await whitelistable.whitelisted.call(user); | ||
assert.isTrue(isWhitelisted); | ||
const whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isTrue(whitelisted); | ||
}); | ||
await whitelistable.unregisterUser(user); | ||
isWhitelisted = await whitelistable.whitelisted.call(user); | ||
assert.isFalse(isWhitelisted); | ||
it('should not be possible to accept ether (fallback function)', async () => { | ||
await expectThrow(whitelistable.sendTransaction({ from: admin, value: 1 })); | ||
}); | ||
it('should be possible to register a user, unregister the user and than register again the user', async () => { | ||
let isWhitelisted = await whitelistable.whitelisted.call(user); | ||
assert.isFalse(isWhitelisted); | ||
it('should not return true in the checkWhitelisted function if one parameter is wrong', async () => { | ||
const contributionLimit = web3.toWei(5); | ||
const hash = contributionHash(user, contributionLimit); | ||
const sig = fromRpcSig(web3.eth.sign(nonAdmin, hash)); | ||
await whitelistable.registerUser(user); | ||
isWhitelisted = await whitelistable.whitelisted.call(user); | ||
assert.isTrue(isWhitelisted); | ||
//wrong contribution limit | ||
let whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
2, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
await whitelistable.unregisterUser(user); | ||
isWhitelisted = await whitelistable.whitelisted.call(user); | ||
assert.isFalse(isWhitelisted); | ||
//wrong contributor address | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
nonAdmin, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
await whitelistable.registerUser(user); | ||
isWhitelisted = await whitelistable.whitelisted.call(user); | ||
assert.isTrue(isWhitelisted); | ||
//wrong signature v | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
nonAdmin, | ||
contributionLimit, | ||
1, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
//wrong signature r | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
nonAdmin, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(3), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
//wrong signature s | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
nonAdmin, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(4) | ||
); | ||
assert.isFalse(whitelisted); | ||
//empty contributor address | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
'', | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
//shifted sig.r and sig.s | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.s), | ||
bufferToHex(sig.r) | ||
); | ||
assert.isFalse(whitelisted); | ||
//shifted sig.r is equalt to sig.s | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.r) | ||
); | ||
assert.isFalse(whitelisted); | ||
//shifted sig.s is equalt to sig.r | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
contributionLimit, | ||
sig.v, | ||
bufferToHex(sig.s), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
//contribution limit is zero | ||
whitelisted = await whitelistable.checkWhitelisted.call( | ||
user, | ||
0, | ||
sig.v, | ||
bufferToHex(sig.r), | ||
bufferToHex(sig.s) | ||
); | ||
assert.isFalse(whitelisted); | ||
}); | ||
}); |
@@ -23,2 +23,8 @@ require('babel-register'); | ||
}, | ||
ganache: { | ||
host: 'localhost', | ||
network_id: '*', | ||
port: 7545, | ||
provider: provider('http://127.0.0.1:7545') | ||
}, | ||
coverage: { | ||
@@ -25,0 +31,0 @@ host: 'localhost', |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
341966
1448
58
3
25
48
1
+ Added@tokenfoundry/state-machine@0.2.4(transitive)
+ Added@tokenfoundry/token-contracts@0.2.11(transitive)
+ Addedajv@6.12.6(transitive)
+ Addedansi-regex@2.1.1(transitive)
+ Addedargparse@1.0.10(transitive)
+ Addedasn1@0.2.6(transitive)
+ Addedassert-plus@1.0.0(transitive)
+ Addedasynckit@0.4.0(transitive)
+ Addedaws-sign2@0.7.0(transitive)
+ Addedaws4@1.13.2(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbcrypt-pbkdf@1.0.2(transitive)
+ Addedbn.js@4.11.6(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedbrowser-stdout@1.3.0(transitive)
+ Addedcamelcase@3.0.0(transitive)
+ Addedcaseless@0.12.0(transitive)
+ Addedcliui@3.2.0(transitive)
+ Addedcode-point-at@1.1.0(transitive)
+ Addedcombined-stream@1.0.8(transitive)
+ Addedcommander@2.9.0(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedcore-util-is@1.0.2(transitive)
+ Addedcoveralls@3.1.1(transitive)
+ Addeddashdash@1.14.1(transitive)
+ Addeddebug@2.6.8(transitive)
+ Addeddecamelize@1.2.0(transitive)
+ Addeddelayed-stream@1.0.0(transitive)
+ Addeddiff@3.2.0(transitive)
+ Addeddotenv@4.0.0(transitive)
+ Addedecc-jsbn@0.1.2(transitive)
+ Addederror-ex@1.3.2(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedethjs-abi@0.2.1(transitive)
+ Addedextend@3.0.2(transitive)
+ Addedextsprintf@1.3.0(transitive)
+ Addedfast-deep-equal@3.1.3(transitive)
+ Addedfast-json-stable-stringify@2.1.0(transitive)
+ Addedfind-up@1.1.2(transitive)
+ Addedforever-agent@0.6.1(transitive)
+ Addedform-data@2.3.3(transitive)
+ Addedfs-extra@0.30.0(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-caller-file@1.0.3(transitive)
+ Addedgetpass@0.1.7(transitive)
+ Addedglob@7.1.17.2.3(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedgraceful-readlink@1.0.1(transitive)
+ Addedgrowl@1.9.2(transitive)
+ Addedhar-schema@2.0.0(transitive)
+ Addedhar-validator@5.1.5(transitive)
+ Addedhas-flag@1.0.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhe@1.1.1(transitive)
+ Addedhosted-git-info@2.8.9(transitive)
+ Addedhttp-signature@1.2.0(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedinvert-kv@1.0.0(transitive)
+ Addedis-arrayish@0.2.1(transitive)
+ Addedis-core-module@2.16.1(transitive)
+ Addedis-fullwidth-code-point@1.0.0(transitive)
+ Addedis-hex-prefixed@1.0.0(transitive)
+ Addedis-typedarray@1.0.0(transitive)
+ Addedis-utf8@0.2.1(transitive)
+ Addedisstream@0.1.2(transitive)
+ Addedjs-sha3@0.5.5(transitive)
+ Addedjs-yaml@3.14.1(transitive)
+ Addedjsbn@0.1.1(transitive)
+ Addedjson-schema@0.4.0(transitive)
+ Addedjson-schema-traverse@0.4.1(transitive)
+ Addedjson-stringify-safe@5.0.1(transitive)
+ Addedjson3@3.3.2(transitive)
+ Addedjsonfile@2.4.0(transitive)
+ Addedjsprim@1.4.2(transitive)
+ Addedklaw@1.3.1(transitive)
+ Addedlcid@1.0.0(transitive)
+ Addedlcov-parse@1.0.0(transitive)
+ Addedload-json-file@1.1.0(transitive)
+ Addedlodash._baseassign@3.2.0(transitive)
+ Addedlodash._basecopy@3.0.1(transitive)
+ Addedlodash._basecreate@3.0.3(transitive)
+ Addedlodash._getnative@3.9.1(transitive)
+ Addedlodash._isiterateecall@3.0.9(transitive)
+ Addedlodash.assign@4.2.0(transitive)
+ Addedlodash.create@3.1.1(transitive)
+ Addedlodash.isarguments@3.1.0(transitive)
+ Addedlodash.isarray@3.0.4(transitive)
+ Addedlodash.keys@3.1.2(transitive)
+ Addedlog-driver@1.2.7(transitive)
+ Addedmemorystream@0.3.1(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedminimist@0.0.81.2.8(transitive)
+ Addedmkdirp@0.5.1(transitive)
+ Addedmocha@3.5.3(transitive)
+ Addedms@2.0.0(transitive)
+ Addednormalize-package-data@2.5.0(transitive)
+ Addednumber-is-nan@1.0.1(transitive)
+ Addednumber-to-bn@1.7.0(transitive)
+ Addedoauth-sign@0.9.0(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedoriginal-require@1.0.1(transitive)
+ Addedos-locale@1.4.0(transitive)
+ Addedparse-json@2.2.0(transitive)
+ Addedpath-exists@2.1.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpath-type@1.1.0(transitive)
+ Addedperformance-now@2.1.0(transitive)
+ Addedpify@2.3.0(transitive)
+ Addedpinkie@2.0.4(transitive)
+ Addedpinkie-promise@2.0.1(transitive)
+ Addedpsl@1.15.0(transitive)
+ Addedpunycode@2.3.1(transitive)
+ Addedqs@6.5.3(transitive)
+ Addedread-pkg@1.1.0(transitive)
+ Addedread-pkg-up@1.0.1(transitive)
+ Addedrequest@2.88.2(transitive)
+ Addedrequire-directory@2.1.1(transitive)
+ Addedrequire-from-string@1.2.1(transitive)
+ Addedrequire-main-filename@1.0.1(transitive)
+ Addedresolve@1.22.10(transitive)
+ Addedrimraf@2.7.1(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedset-blocking@2.0.0(transitive)
+ Addedsolc@0.4.19(transitive)
+ Addedspdx-correct@3.2.0(transitive)
+ Addedspdx-exceptions@2.5.0(transitive)
+ Addedspdx-expression-parse@3.0.1(transitive)
+ Addedspdx-license-ids@3.0.21(transitive)
+ Addedsprintf-js@1.0.3(transitive)
+ Addedsshpk@1.18.0(transitive)
+ Addedstring-width@1.0.2(transitive)
+ Addedstrip-ansi@3.0.1(transitive)
+ Addedstrip-bom@2.0.0(transitive)
+ Addedstrip-hex-prefix@1.0.0(transitive)
+ Addedsupports-color@3.1.2(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedtough-cookie@2.5.0(transitive)
+ Addedtruffle@4.1.3(transitive)
+ Addedtunnel-agent@0.6.0(transitive)
+ Addedtweetnacl@0.14.5(transitive)
+ Addeduri-js@4.4.1(transitive)
+ Addeduuid@3.4.0(transitive)
+ Addedvalidate-npm-package-license@3.0.4(transitive)
+ Addedverror@1.10.0(transitive)
+ Addedwhich-module@1.0.0(transitive)
+ Addedwindow-size@0.2.0(transitive)
+ Addedwrap-ansi@2.1.0(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedy18n@3.2.2(transitive)
+ Addedyargs@4.8.1(transitive)
+ Addedyargs-parser@2.4.1(transitive)
+ Addedzeppelin-solidity@1.7.0(transitive)
- Removed@tokenfoundry/state-machine@0.1.2(transitive)
- Removedzeppelin-solidity@1.12.0(transitive)
Updatedzeppelin-solidity@1.7.0