selfkey-token
Version:
The SelfKey Token is an ERC20 standard token that is used to fuel the SelfKey KYC dapps
519 lines (427 loc) • 20.1 kB
JavaScript
const SelfKeyCrowdsale = artifacts.require('./SelfKeyCrowdsale.sol')
const SelfKeyToken = artifacts.require('./SelfKeyToken.sol')
const TokenTimelock = artifacts.require(
'zeppelin-solidity/contracts/token/TokenTimelock.sol'
)
const RefundVault = artifacts.require(
'zeppelin-solidity/contracts/crowdsale/RefundVault.sol'
)
const assertThrows = require('./utils/assertThrows')
const timeTravel = require('./utils/timeTravel')
const { goal } = require('./utils/common')
contract('SelfKeyCrowdsale', accounts => {
const now = new Date().getTime() / 1000
const start = now
const end = start + 1296000 // 15 days after start
const SIGNIFICANT_AMOUNT = 2048
const [
buyer,
buyer2,
buyer3,
buyer4,
buyer5,
receiver,
middleman
] = accounts.slice(1)
let crowdsaleContract
let tokenContract
let foundersTimelock1
let foundersTimelock2
let foundationTimelock
let vaultContract
context("Crowdsale whose goal hasn't been reached", () => {
const hugeGoal = 950000000000000000000000000
const sendAmount = web3.toWei(3, 'ether')
before(async () => {
crowdsaleContract = await SelfKeyCrowdsale.new(start, end, hugeGoal)
const token = await crowdsaleContract.token.call()
tokenContract = await SelfKeyToken.at(token)
const foundersTimelock1Address = await crowdsaleContract.foundersTimelock1.call()
const foundersTimelock2Address = await crowdsaleContract.foundersTimelock2.call()
const foundationTimelockAddress = await crowdsaleContract.foundationTimelock.call()
foundersTimelock1 = await TokenTimelock.at(foundersTimelock1Address)
foundersTimelock2 = await TokenTimelock.at(foundersTimelock2Address)
foundationTimelock = await TokenTimelock.at(foundationTimelockAddress)
const vaultAddress = await crowdsaleContract.vault.call()
vaultContract = await RefundVault.at(vaultAddress)
})
it('deployed with the right owner', async () => {
assert.isNotNull(crowdsaleContract)
assert.isNotNull(tokenContract)
const owner = await tokenContract.owner.call()
assert.equal(owner, crowdsaleContract.address)
})
it('allows refunds', async () => {
// Purchase equivalent of <sendAmount> in tokens
const REFUNDING = 1
const sender = buyer
await crowdsaleContract.verifyKYC(sender)
await crowdsaleContract.sendTransaction({
from: sender,
value: sendAmount
})
// check no refund can be claimed before finalization
await assertThrows(crowdsaleContract.claimRefund(sender))
// finalize sale and check it was not successful
await crowdsaleContract.finalize()
const goalReached = await crowdsaleContract.goalReached.call()
assert.isFalse(goalReached)
const vaultState = await vaultContract.state.call()
assert.equal(vaultState, REFUNDING)
// issue refund
const balance1 = web3.eth.getBalance(sender)
await crowdsaleContract.claimRefund(sender)
const balance2 = web3.eth.getBalance(sender)
// check buyer balance increases
assert.isAbove(balance2.toNumber(), balance1.toNumber())
})
})
context('Regular Crowdsale', () => {
before(async () => {
crowdsaleContract = await SelfKeyCrowdsale.new(start, end, goal)
const token = await crowdsaleContract.token.call()
tokenContract = await SelfKeyToken.at(token)
const foundersTimelock1Address = await crowdsaleContract.foundersTimelock1.call()
const foundersTimelock2Address = await crowdsaleContract.foundersTimelock2.call()
const foundationTimelockAddress = await crowdsaleContract.foundationTimelock.call()
foundersTimelock1 = await TokenTimelock.at(foundersTimelock1Address)
foundersTimelock2 = await TokenTimelock.at(foundersTimelock2Address)
foundationTimelock = await TokenTimelock.at(foundationTimelockAddress)
const vaultAddress = await crowdsaleContract.vault.call()
vaultContract = await RefundVault.at(vaultAddress)
})
it('deploys and instantiates all derived contracts', async () => {
assert.isNotNull(crowdsaleContract)
assert.isNotNull(tokenContract)
assert.isNotNull(vaultContract)
assert.isNotNull(foundersTimelock1)
assert.isNotNull(foundersTimelock2)
assert.isNotNull(foundationTimelock)
// check token contract owner is the crowdsale contract
const owner = await tokenContract.owner.call()
assert.equal(owner, crowdsaleContract.address)
})
it('distributed the initial token amounts correctly', async () => {
// Get allocation wallet addresses
const foundationPool = await crowdsaleContract.FOUNDATION_POOL_ADDR.call()
const communityPool = await crowdsaleContract.COMMUNITY_POOL_ADDR.call()
const legalExpenses1Address = await crowdsaleContract.LEGAL_EXPENSES_ADDR_1.call()
const legalExpenses2Address = await crowdsaleContract.LEGAL_EXPENSES_ADDR_2.call()
const foundersPool = await crowdsaleContract.FOUNDERS_POOL_ADDR.call()
const foundersTimelock1Address = await crowdsaleContract.foundersTimelock1.call()
const foundersTimelock2Address = await crowdsaleContract.foundersTimelock2.call()
const foundationTimelockAddress = await crowdsaleContract.foundationTimelock.call()
// Get expected token amounts from contract config
const expectedFoundationTokens = await crowdsaleContract.FOUNDATION_POOL_TOKENS.call()
const expectedCommunityTokens = await crowdsaleContract.COMMUNITY_POOL_TOKENS.call()
const expectedLegal1Tokens = await crowdsaleContract.LEGAL_EXPENSES_1_TOKENS.call()
const expectedLegal2Tokens = await crowdsaleContract.LEGAL_EXPENSES_2_TOKENS.call()
const expectedFoundersTokens = await crowdsaleContract.FOUNDERS_TOKENS.call()
const expectedFoundersVested1 = await crowdsaleContract.FOUNDERS_TOKENS_VESTED_1.call()
const expectedFoundersVested2 = await crowdsaleContract.FOUNDERS_TOKENS_VESTED_2.call()
const expectedFoundationVested = await crowdsaleContract.FOUNDATION_POOL_TOKENS_VESTED.call()
// Get actual balances
const foundationBalance = await tokenContract.balanceOf.call(
foundationPool
)
const communityBalance = await tokenContract.balanceOf.call(communityPool)
const legal1Balance = await tokenContract.balanceOf.call(
legalExpenses1Address
)
const legal2Balance = await tokenContract.balanceOf.call(
legalExpenses2Address
)
const foundersBalance = await tokenContract.balanceOf.call(foundersPool)
const foundersVestedBalance1 = await tokenContract.balanceOf.call(
foundersTimelock1Address
)
const foundersVestedBalance2 = await tokenContract.balanceOf.call(
foundersTimelock2Address
)
const foundationVestedBalance = await tokenContract.balanceOf.call(
foundationTimelockAddress
)
// Check allocation was done as expected
assert.equal(
foundationBalance.toNumber(),
expectedFoundationTokens.toNumber()
)
assert.equal(
communityBalance.toNumber(),
expectedCommunityTokens.toNumber()
)
assert.equal(legal1Balance.toNumber(), expectedLegal1Tokens.toNumber())
assert.equal(legal2Balance.toNumber(), expectedLegal2Tokens.toNumber())
assert.equal(
foundersBalance.toNumber(),
expectedFoundersTokens.toNumber()
)
assert.equal(
foundersVestedBalance1.toNumber(),
expectedFoundersVested1.toNumber()
)
assert.equal(
foundersVestedBalance2.toNumber(),
expectedFoundersVested2.toNumber()
)
assert.equal(
foundationVestedBalance.toNumber(),
expectedFoundationVested.toNumber()
)
})
it('cannot change start time if sale already started', async () => {
await assertThrows(crowdsaleContract.setStartTime(start + 999))
})
it('allows KYC verification of participant address', async () => {
// check address is initially unverified
const sender = buyer
let verified = await crowdsaleContract.kycVerified.call(sender)
assert.isFalse(verified)
// verify KYC status for buyer address
await crowdsaleContract.verifyKYC(sender)
// check the address is now verified
verified = await crowdsaleContract.kycVerified.call(sender)
assert.isTrue(verified)
})
it('does not allow purchase for unverified participants', async () => {
const sender = buyer2
const sendAmount = web3.toWei(2, 'ether')
// attempt sending ETH from an unverified address
const verified = await crowdsaleContract.kycVerified.call(sender)
assert.isFalse(verified)
await assertThrows(
crowdsaleContract.sendTransaction({ from: sender, value: sendAmount })
)
})
it('allows token purchases for verified participants', async () => {
const sender = buyer3
const vaultInitialBalance = await vaultContract.deposited.call(sender)
const rate = await crowdsaleContract.rate.call()
// verify buyer address
await crowdsaleContract.verifyKYC(sender)
const verified = await crowdsaleContract.kycVerified.call(sender)
assert.isTrue(verified)
// send ETH to the contract to purchase tokens
const sendAmount = web3.toWei(2, 'ether')
await crowdsaleContract.sendTransaction({
from: sender,
value: sendAmount
})
const buyerBalance = await tokenContract.balanceOf.call(sender)
// check allocated amount corresponds to the set exchange rate according to ETH price
assert.equal(buyerBalance.toNumber(), sendAmount * rate)
// Check wei added to the vault is correct
const vaultNewBalance = await vaultContract.deposited.call(sender)
assert.equal(
vaultNewBalance.toNumber() - vaultInitialBalance.toNumber(),
sendAmount
)
})
it('does not allow contributions below minimum cap per purchaser', async () => {
const sender = buyer4
const minTokenCap = await crowdsaleContract.PURCHASER_MIN_TOKEN_CAP.call()
const rate = await crowdsaleContract.rate.call()
const minWei = minTokenCap.toNumber() / rate.toNumber()
const sendAmount = minWei - SIGNIFICANT_AMOUNT
// verify new purchaser
await crowdsaleContract.verifyKYC(sender)
const verified = await crowdsaleContract.kycVerified.call(sender)
assert.isTrue(verified)
// check below cap transaction fails
await assertThrows(
crowdsaleContract.sendTransaction({ from: sender, value: sendAmount })
)
})
it('does allow contributions above minimum purchaser cap', async () => {
const sender = buyer4
const minTokenCap = await crowdsaleContract.PURCHASER_MIN_TOKEN_CAP.call()
const rate = await crowdsaleContract.rate.call()
const minWei = minTokenCap.toNumber() / rate.toNumber()
const sendAmount = minWei + SIGNIFICANT_AMOUNT
const balance1 = await tokenContract.balanceOf(sender)
await crowdsaleContract.sendTransaction({
from: sender,
value: sendAmount
})
const balance2 = await tokenContract.balanceOf(sender)
assert.isAbove(balance2.toNumber(), balance1.toNumber())
})
it('does not allow contributions above $3000 per purchaser on day 1', async () => {
const sender = buyer4
const maxTokenCap = await crowdsaleContract.PURCHASER_MAX_TOKEN_CAP_DAY1.call()
const rate = await crowdsaleContract.rate.call()
const maxWei = maxTokenCap.toNumber() / rate.toNumber()
const sendAmount = maxWei
await assertThrows(
crowdsaleContract.sendTransaction({ from: sender, value: sendAmount })
)
})
it('allows contributions above $3000 after day 1', async () => {
const sender = buyer4
timeTravel(86400) // fast forward 1 day
const maxTokenCap = await crowdsaleContract.PURCHASER_MAX_TOKEN_CAP_DAY1.call()
const rate = await crowdsaleContract.rate.call()
const maxWei = maxTokenCap.toNumber() / rate.toNumber()
const sendAmount = maxWei + SIGNIFICANT_AMOUNT
const balance1 = await tokenContract.balanceOf(sender)
await crowdsaleContract.sendTransaction({
from: sender,
value: sendAmount
})
const balance2 = await tokenContract.balanceOf(sender)
assert.isAbove(balance2.toNumber(), balance1.toNumber())
})
it('does not allow contributions above $18000 after day 1', async () => {
const sender = buyer5
const maxTokenCap = await crowdsaleContract.PURCHASER_MAX_TOKEN_CAP.call()
const rate = await crowdsaleContract.rate.call()
const maxWei = maxTokenCap.toNumber() / rate.toNumber()
let sendAmount = maxWei + SIGNIFICANT_AMOUNT
// verify new purchaser
await crowdsaleContract.verifyKYC(sender)
const verified = await crowdsaleContract.kycVerified.call(sender)
assert.isTrue(verified)
// check transaction fails because purchase is above cap
await assertThrows(
crowdsaleContract.sendTransaction({ from: sender, value: sendAmount })
)
// check participant can still purchase slightly above the max cap
sendAmount = maxWei - SIGNIFICANT_AMOUNT
const balance1 = await tokenContract.balanceOf(sender)
crowdsaleContract.sendTransaction({ from: sender, value: sendAmount })
const balance2 = await tokenContract.balanceOf(sender)
assert.isAbove(balance2.toNumber(), balance1.toNumber())
})
it('does not allow updating ETH price if sale has already started', () => {
assertThrows(crowdsaleContract.setEthPrice(999))
})
it('can change the end date if sale has not ended', async () => {
const additionalTime = 999
const beforeEnd = await crowdsaleContract.endTime.call()
await crowdsaleContract.setEndTime(beforeEnd.toNumber() + additionalTime)
const laterEnd = await crowdsaleContract.endTime.call()
assert.equal(laterEnd.toNumber(), beforeEnd.toNumber() + additionalTime)
await assertThrows(crowdsaleContract.setEndTime(now - 999))
await assertThrows(crowdsaleContract.setEndTime(start - 1))
})
it('does not allow contributions after end date', async () => {
const sender = buyer
// fast forwards until crowdsale end time
const untilEnd = end - now
timeTravel(untilEnd)
// check buyer is verified
const verified = await crowdsaleContract.kycVerified.call(sender)
assert.isTrue(verified)
// check transaction fails
const sendAmount = web3.toWei(1, 'ether')
await assertThrows(
crowdsaleContract.sendTransaction({ from: sender, value: sendAmount })
)
})
it('cannot change end time if sale has already ended', async () => {
await assertThrows(crowdsaleContract.setEndTime(end + 999))
})
it('does not allow token transfers before crowdsale is finalized', async () => {
const sender = buyer3
const sendAmount = 5
// check participant has enough token funds
const balance = await tokenContract.balanceOf.call(sender)
assert.isAtLeast(balance.toNumber(), sendAmount)
// Tokens are not yet transferrable because sale has not been finalized
await assertThrows(
tokenContract.transfer(receiver, sendAmount, { from: sender })
)
})
it('can finalize token sale successfully', async () => {
const crowdsaleWallet = await crowdsaleContract.CROWDSALE_WALLET_ADDR.call()
const vaultBalance = web3.eth.getBalance(vaultContract.address)
const walletBalance1 = web3.eth.getBalance(crowdsaleWallet)
// finalize token sale
await crowdsaleContract.finalize()
const walletBalance2 = web3.eth.getBalance(crowdsaleWallet)
const vaultBalance2 = web3.eth.getBalance(vaultContract.address)
const contractTokenBalance = await tokenContract.balanceOf.call(
crowdsaleContract.address
)
// check unsold tokens were effectively burned
assert.equal(contractTokenBalance, 0)
// check all ETH was effectively transferred to the crowdsale wallet
assert.equal(vaultBalance2, 0)
assert.equal(
walletBalance2.toNumber(),
walletBalance1.toNumber() + vaultBalance.toNumber()
)
})
it('does not allow finalize to be re-invoked', async () => {
await assertThrows(crowdsaleContract.finalize())
})
it('enables token transfers after finalization', async () => {
const sender = buyer3
const sendAmount = 9 // KEY
// check sender has enough tokens
const senderBalance = await tokenContract.balanceOf(sender)
assert.isAtLeast(senderBalance.toNumber(), sendAmount)
// test transfer method
let receiverBalance1 = await tokenContract.balanceOf.call(receiver)
await tokenContract.transfer(receiver, sendAmount, { from: sender })
let receiverBalance2 = await tokenContract.balanceOf.call(receiver)
assert.equal(
receiverBalance2.toNumber() - receiverBalance1.toNumber(),
sendAmount
)
// approve a middleman to make transfer on behalf of sender
await tokenContract.approve(middleman, sendAmount, { from: sender })
const senderBalance1 = await tokenContract.balanceOf.call(sender)
receiverBalance1 = await tokenContract.balanceOf.call(receiver)
// test unsuccessful transferFrom invocation (above the approved amount)
await assertThrows(
tokenContract.transferFrom(sender, receiver, sendAmount + 1, {
from: middleman
})
) // function-paren-newline
// test successful transferFrom invocation
await tokenContract.transferFrom(sender, receiver, sendAmount, {
from: middleman
})
const senderBalance2 = await tokenContract.balanceOf.call(sender)
receiverBalance2 = await tokenContract.balanceOf.call(receiver)
assert.equal(senderBalance1.minus(senderBalance2), sendAmount)
assert.equal(receiverBalance2.minus(receiverBalance1), sendAmount)
})
it('should allow the release of locked tokens for founders and foundation', async () => {
const sixMonths = 15552000
const foundersPool = await crowdsaleContract.FOUNDERS_POOL_ADDR.call()
const foundersExpected1 = await crowdsaleContract.FOUNDERS_TOKENS_VESTED_1.call()
const foundersExpected2 = await crowdsaleContract.FOUNDERS_TOKENS_VESTED_2.call()
const vestedExpected = await crowdsaleContract.FOUNDATION_POOL_TOKENS_VESTED.call()
// forward time 6 months
await timeTravel(sixMonths)
// test first timelock release
const foundersBalance1 = await tokenContract.balanceOf(foundersPool)
await crowdsaleContract.releaseLockFounders1()
const foundersBalance2 = await tokenContract.balanceOf(foundersPool)
assert.equal(
foundersBalance2.toNumber(),
foundersBalance1.toNumber() + foundersExpected1.toNumber()
)
// pre-commitment half-vested locks can be tested here as well
// forward time an additional 6 months
await timeTravel(sixMonths)
// test second timelock release
await crowdsaleContract.releaseLockFounders2()
const foundersBalance3 = await tokenContract.balanceOf(foundersPool)
assert.equal(
foundersBalance3.toNumber(),
foundersBalance2.toNumber() + foundersExpected2.toNumber()
)
// check for second foundation vested release
const vestedAddress = await crowdsaleContract.FOUNDATION_POOL_ADDR_VEST.call()
const vestedBalance1 = await tokenContract.balanceOf(vestedAddress)
await crowdsaleContract.releaseLockFoundation()
const vestedBalance2 = await tokenContract.balanceOf(vestedAddress)
const newExpectedBalance =
vestedBalance1.toNumber() + vestedExpected.toNumber()
assert.equal(vestedBalance2.toNumber(), newExpectedBalance)
})
})
})