Skip to content

Commit

Permalink
Merge 7212738 into d666394
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-lough committed Nov 22, 2017
2 parents d666394 + 7212738 commit 709cfba
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 36 deletions.
47 changes: 35 additions & 12 deletions contracts/PolyMathToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ pragma solidity ^0.4.13;

import 'zeppelin-solidity/contracts/token/PausableToken.sol';
import 'zeppelin-solidity/contracts/token/BurnableToken.sol';
import 'zeppelin-solidity/contracts/token/ERC20Basic.sol';
import './PolyMathTokenOffering.sol';

contract PolyMathToken is PausableToken, BurnableToken {

Expand All @@ -15,17 +17,21 @@ contract PolyMathToken is PausableToken, BurnableToken {
// 1 billion POLY tokens in units divisible up to 18 decimals.
uint256 public constant INITIAL_SUPPLY = 1000 * (10**6) * token_factor;

uint256 public constant PRESALE_SUPPLY = 150000000 * token_factor;
uint256 public constant PUBLICSALE_SUPPLY = 150000000 * token_factor;
uint256 public constant PRESALE_SUPPLY = 200000000 * token_factor;
uint256 public constant PUBLICSALE_SUPPLY = 120000000 * token_factor;
uint256 public constant FOUNDER_SUPPLY = 150000000 * token_factor;
uint256 public constant BDMARKET_SUPPLY = 25000000 * token_factor;
uint256 public constant BDMARKET_SUPPLY = 55000000 * token_factor;
uint256 public constant ADVISOR_SUPPLY = 25000000 * token_factor;
uint256 public constant RESERVE_SUPPLY = 500000000 * token_factor;
uint256 public constant RESERVE_SUPPLY = 450000000 * token_factor;

bool private crowdsaleInitialized = false;
address private crowdsale;

function isCrowdsaleAddressSet() public constant returns (bool) {
return (address(crowdsale) != address(0));
}

modifier crowdsaleNotInitialized() {
require(crowdsaleInitialized == false);
require(!isCrowdsaleAddressSet());
_;
}

Expand All @@ -41,22 +47,39 @@ contract PolyMathToken is PausableToken, BurnableToken {
}

function initializeCrowdsale(address _crowdsale) onlyOwner crowdsaleNotInitialized {
crowdsaleInitialized = true;
transfer(_crowdsale, PUBLICSALE_SUPPLY);
crowdsale = _crowdsale;
pause();
transferOwnership(_crowdsale);
}

function issueTokens(address _to, uint256 _value) onlyOwner returns (bool) {
balances[owner] = balances[owner].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(owner, _to, _value);
return true;
function getBlockTimestamp() internal constant returns (uint256) {
return block.timestamp;
}

// Override - lifecycle/Pausable.sol
function unpause() public {
if (PolyMathTokenOffering(crowdsale).hasEnded()) {
// Tokens should be locked until 7 days after the crowdsale
require(getBlockTimestamp() >= (PolyMathTokenOffering(crowdsale).endTime() + 7 days));
}
super.unpause();
}

// Don't accept calls to the contract address; must call a method.
function () {
revert();
}

function claimTokens(address _token) public onlyOwner {
if (_token == 0x0) {
owner.transfer(this.balance);
return;
}

ERC20Basic token = ERC20Basic(_token);
uint256 balance = token.balanceOf(this);
token.transfer(owner, balance);
}

}
24 changes: 21 additions & 3 deletions contracts/PolyMathTokenOffering.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pragma solidity ^0.4.13;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'zeppelin-solidity/contracts/math/SafeMath.sol';
import 'zeppelin-solidity/contracts/token/ERC20Basic.sol';
import './PolyMathToken.sol';

/**
Expand Down Expand Up @@ -143,6 +144,7 @@ contract PolyMathTokenOffering is Ownable {
uint256 weiToReturn = msg.value.sub(weiAmount);
uint256 tokens = ethToTokens(weiAmount);

token.unpause();
weiRaised = weiRaised.add(weiAmount);

forwardFunds(weiAmount);
Expand All @@ -153,7 +155,8 @@ contract PolyMathTokenOffering is Ownable {
}
// send tokens to purchaser
TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);
token.issueTokens(beneficiary, tokens);
token.transfer(beneficiary, tokens);
token.pause();
TokenRedeem(beneficiary, tokens);
checkFinalize();
}
Expand Down Expand Up @@ -182,6 +185,9 @@ contract PolyMathTokenOffering is Ownable {

// @return true if crowdsale event has ended or cap reached
function hasEnded() public constant returns (bool) {
if (isFinalized) {
return true;
}
bool capReached = weiRaised >= cap;
bool passedEndTime = getBlockTimestamp() > endTime;
return passedEndTime || capReached;
Expand All @@ -201,14 +207,13 @@ contract PolyMathTokenOffering is Ownable {
require(!isFinalized);
Finalized();
isFinalized = true;
token.unpause();
token.transferOwnership(owner);
}

// Allows the owner to take back the tokens that are assigned to the sale contract.
event TokensRefund(uint256 _amount);
function refund() external onlyOwner returns (bool) {
require(isFinalized);
require(hasEnded());
uint256 tokens = token.balanceOf(address(this));

if (tokens == 0) {
Expand All @@ -221,4 +226,17 @@ contract PolyMathTokenOffering is Ownable {

return true;
}

function claimTokens(address _token) public onlyOwner {
require(hasEnded());
if (_token == 0x0) {
owner.transfer(this.balance);
return;
}

ERC20Basic refundToken = ERC20Basic(_token);
uint256 balance = refundToken.balanceOf(this);
refundToken.transfer(owner, balance);
TokensRefund(balance);
}
}
9 changes: 5 additions & 4 deletions migrations/2_deploy_contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,26 @@ const duration = {
};

module.exports = function(deployer, network) {

const startTime = latestTime() + duration.minutes(5);
const endTime = startTime + duration.weeks(1);
const rate = new web3.BigNumber(1000);
const wallet = web3.eth.accounts[0];
const presale_wallet = web3.eth.accounts[1];
const goal = new web3.BigNumber(3000 * Math.pow(10, 18));
const cap = new web3.BigNumber(15000 * Math.pow(10, 18));

if(network !== 'development'){
deployer.deploy(POLYToken, wallet).then(async function() {
let tokenDeployed = await POLYToken.deployed();
let tokenDeployed = await POLYToken.deployed(presale_wallet);
const encodedPoly = abiEncoder.rawEncode(['address'], [ wallet]);
console.log('encodedPoly ENCODED: \n', encodedPoly.toString('hex'));
// function PolyMathTokenOffering(address _token, uint256 _startTime, uint256 _endTime, uint256 _cap, address _wallet) {
await deployer.deploy(POLYTokenOffering, POLYToken.address, startTime, endTime, cap, wallet);
const encodedPOLYTokenOffering = abiEncoder.rawEncode(['address', 'uint256', 'uint256', 'uint256', 'address'], [POLYToken.address, startTime.toString(10), endTime.toString(10), cap.toString(10), wallet]);
console.log('encodedPOLYTokenOffering ENCODED: \n', encodedPOLYTokenOffering.toString('hex'));
await tokenDeployed.setOwner(POLYTokenOffering.address);
await tokenDeployed.initializeCrowdsale(POLYTokenOffering.address);

});
}
};
118 changes: 108 additions & 10 deletions test/auditTests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
var TokenOffering = artifacts.require('./helpers/PolyMathTokenOfferingMock.sol');
var POLYToken = artifacts.require('PolyMathToken.sol');
var POLYToken = artifacts.require('./helpers/PolyMathTokenMock.sol');

import { latestTime, duration } from './helpers/latestTime';
const BigNumber = require("bignumber.js");
Expand Down Expand Up @@ -30,14 +30,92 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p
});
});

it('Cannot deploy token if the presale wallet address is null', async function () {
await assertFail(async () => {
tokenDeployed = await POLYToken.new(0x0);
});
});

it('Cannot deploy crowdsale if the cap is 0', async function () {
tokenDeployed = await POLYToken.new(presale_wallet);
await assertFail(async () => {
tokenOfferingDeployed = await TokenOffering.new(
tokenDeployed.address,
latestTime() + duration.seconds(17),
latestTime() + duration.weeks(3),
0,
presale_wallet
)
});
});

it('Cannot deploy crowdsale if the token is null', async function () {
await assertFail(async () => {
tokenOfferingDeployed = await TokenOffering.new(
0x0,
latestTime() + duration.seconds(15),
latestTime() + duration.weeks(6),
12345,
presale_wallet
)
});
});

it('Cannot deploy crowdsale if the token is null', async function () {
tokenDeployed = await POLYToken.new(presale_wallet);
await assertFail(async () => {
tokenOfferingDeployed = await TokenOffering.new(
tokenDeployed.address,
latestTime() + duration.seconds(19),
latestTime() + duration.weeks(2),
12345,
0x0
)
});
});

it('Cannot deploy crowdsale if the start time is in the past', async function () {
tokenDeployed = await POLYToken.new(presale_wallet);
await assertFail(async () => {
tokenOfferingDeployed = await TokenOffering.new(
tokenDeployed.address,
latestTime() - duration.seconds(2),
latestTime() + duration.weeks(1),
12345,
presale_wallet
)
});
});

it('Cannot deploy crowdsale if the end time is before the start time', async function () {
tokenDeployed = await POLYToken.new(presale_wallet);
await assertFail(async () => {
tokenOfferingDeployed = await TokenOffering.new(
tokenDeployed.address,
latestTime() + duration.weeks(10),
latestTime() + duration.weeks(1),
12345,
presale_wallet
)
});
});

it('Cap should not be able to exceed balance of crowdsale contract', async function () {
tokenDeployed = await POLYToken.new(presale_wallet);
await assertFail(async () => { await TokenOffering.new(tokenDeployed.address, latestTime() + duration.seconds(20), latestTime() + duration.weeks(1), web3.toWei(150000001, 'ether'), crowdsale_wallet) });
await assertFail(async () => {
await TokenOffering.new(
tokenDeployed.address,
latestTime() + duration.seconds(20),
latestTime() + duration.weeks(1),
web3.toWei(150000001, 'ether'),
presale_wallet
)
});
});

it('Tokens should not be able to be sent to the null address from the token contract', async function () {
tokenDeployed = await POLYToken.new(presale_wallet);
await assertFail(async () => { await tokenDeployed.issueTokens(0x0, tokenDeployed.address) });
await assertFail(async () => { await tokenDeployed.transfer(0x0, tokenDeployed.address) });
});

describe('Deploy Contracts', async function () {
Expand All @@ -50,21 +128,27 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p
tokenOfferingDeployed = await TokenOffering.new(tokenDeployed.address, startTime, endTime, cap, crowdsale_wallet);
});

it('Calling an invalid function on the token triggers the fallback and reverts', async function () {
await assertFail(async () => {
await tokenDeployed.sendTransaction({ from: investor })
});
});

it('Crowdsale should only be able to be initialized once', async function () {
await tokenDeployed.initializeCrowdsale(tokenOfferingDeployed.address);
await assertFail(async () => { await tokenDeployed.initializeCrowdsale(tokenOfferingDeployed.address) });;
});

it('After deploying the Token and the Crowdsale, the balances should all be correct', async function () {
assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 850000000 * 10 ** DECIMALS, "The Token deployer should hold 850mil");
assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 800000000 * 10 ** DECIMALS, "The Token deployer should hold 800mil");
assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 0, "The Crowdsale should have no balance");
assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 150000000 * 10 ** DECIMALS, "The Presale should hold 150mil");
assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 200000000 * 10 ** DECIMALS, "The Presale should hold 200mil");

await tokenDeployed.initializeCrowdsale(tokenOfferingDeployed.address);

assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 700000000 * 10 ** DECIMALS, "The Token deployer should hold 700mil");
assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 150000000 * 10 ** DECIMALS, "The Crowdsale should hold 150mil");
assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 150000000 * 10 ** DECIMALS, "The Presale should hold 150mil");
assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 680000000 * 10 ** DECIMALS, "The Token deployer should hold 680mil");
assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 120000000 * 10 ** DECIMALS, "The Crowdsale should hold 120mil");
assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 200000000 * 10 ** DECIMALS, "The Presale should hold 200mil");
});

describe('Initialize crowdsale', async function () {
Expand All @@ -78,10 +162,24 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p
await assertFail(async () => { await tokenOfferingDeployed.refund() });
});

it('Unsold tokens should be refundable after the crowdsale is finished', async function () {
it('Tokens should be locked until 7 days after crowdsale ends', async function () {
await tokenOfferingDeployed.setBlockTimestamp(endTime + 1);
await tokenOfferingDeployed.checkFinalize();
assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 150000000 * 10 ** DECIMALS, "The Crowdsale should have 150mil");
await assertFail(async () => { await tokenOfferingDeployed.refund() });
await assertFail(async () => { await tokenDeployed.unpause() });
await assertFail(async () => { await tokenOfferingDeployed.refund() });
await tokenDeployed.setBlockTimestamp(endTime + duration.days(7));
await tokenDeployed.unpause();
await tokenOfferingDeployed.refund();
assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 0, "The Crowdsale should have no balance after a refund");
});

it('Unsold tokens should be refundable after the crowdsale is finished and 7 days pass', async function () {
await tokenOfferingDeployed.setBlockTimestamp(endTime + duration.days(7));
await tokenDeployed.setBlockTimestamp(endTime + duration.days(7));
await tokenOfferingDeployed.checkFinalize();
assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 120000000 * 10 ** DECIMALS, "The Crowdsale should have 120mil");
await tokenDeployed.unpause();
await tokenOfferingDeployed.refund();
assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 0, "The Crowdsale should have no balance after a refund");
});
Expand Down
Loading

0 comments on commit 709cfba

Please sign in to comment.