Skip to content

Commit

Permalink
Added approve() allocation based crowdsale. Added burning to the crow…
Browse files Browse the repository at this point in the history
…dsale token.
  • Loading branch information
miohtama committed May 17, 2017
1 parent bd8a905 commit 3f2c2bc
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,20 @@ pragma solidity ^0.4.8;
import "./Crowdsale.sol";

/**
* PreICO crowdsale contract.
* A crowdsale that is selling tokens from a preallocated pool
*
*
* Intended usage
*
* - Tokens have precreated supply "premined"
* - Small share of tokens of the actual crowdsale
* - A short time window
* - Flat price
* - beneficiary is the party who is supplying the tokens for this ICO
*
* - Token owner must transfer sellable tokens to the crowdsale contract using ERC20.approve()
*
*/
contract PreminedCappedCrowdsale is Crowdsale {

/** How many ETH in max we are allowed to raise */
uint public weiCap;
contract AllocatedCrowdsale is Crowdsale {

/* The party who holds the full token pool and has approve()'ed tokens for this crowdsale */
address public beneficiary;

function PreminedCappedCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal, uint _weiCap, address _beneficiary) Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) {
weiCap = _weiCap;
function AllocatedCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal, address _beneficiary) Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) {
beneficiary = _beneficiary;
}

Expand All @@ -40,6 +32,9 @@ contract PreminedCappedCrowdsale is Crowdsale {
}
}

/**
* We are sold out when our approve pool becomes empty.
*/
function isCrowdsaleFull() public constant returns (bool) {
return getTokensLeft() == 0;
}
Expand All @@ -51,8 +46,12 @@ contract PreminedCappedCrowdsale is Crowdsale {
return token.allowance(owner, this);
}

/**
* Transfer tokens from approve() pool to the buyer.
*
* Use approve() given to this crowdsale to distribute the tokens.
*/
function assignTokens(address receiver, uint tokenAmount) private {
// Use approve() given to this crowdsale to distribute the tokens
if(!token.transferFrom(beneficiary, receiver, tokenAmount)) throw;
}
}
16 changes: 16 additions & 0 deletions contracts/BurnableCrowdsaleToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pragma solidity ^0.4.8;

import "./BurnableToken.sol";
import "./CrowdsaleToken.sol";

/**
* A crowdsaled token that you can also burn.
*
*/
contract BurnableCrowdsaleToken is BurnableToken, CrowdsaleToken {

function BurnableCrowdsaleToken(string _name, string _symbol, uint _initialSupply, uint _decimals)
CrowdsaleToken(_name, _symbol, _initialSupply, _decimals) {

}
}
32 changes: 32 additions & 0 deletions contracts/Crowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,38 @@ contract Crowdsale is Haltable {
Invested(receiver, weiAmount, tokenAmount, customerId);
}

/**
* Preallocate tokens for the early investors.
*
* Preallocated tokens have been sold before the actual crowdsale opens.
* This function mints the tokens and moves the crowdsale needle.
*
* Investor count is not handled; it is assumed this goes for multiple investors
* and the token distribution happens outside the smart contract flow.
*
* No money is exchanged, as the crowdsale team already have received the payment.
*
* @param fullTokens tokens as full tokens - decimal places added internally
* @param weiPrice Price of a single full token in wei
*
*/
function preallocate(address receiver, uint fullTokens, uint weiPrice) public onlyOwner {

uint tokenAmount = fullTokens * 10**token.decimals();
uint weiAmount = weiPrice * tokenAmount; // This can be also 0, we give out tokens for free

weiRaised = weiRaised.plus(weiAmount);
tokensSold = tokensSold.plus(tokenAmount);

investedAmountOf[receiver] = investedAmountOf[receiver].plus(weiAmount);
tokenAmountOf[receiver] = tokenAmountOf[receiver].plus(tokenAmount);

assignTokens(receiver, tokenAmount);

// Tell us invest was success
Invested(receiver, weiAmount, tokenAmount, 0);
}

/**
* Allow anonymous contributions to this crowdsale.
*/
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/TestSolidityAddressHash.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

pragma solidity ^0.4.8;

/**
* Test address hash behavior with left and right padded zeroes.
Expand Down
181 changes: 181 additions & 0 deletions ico/tests/contracts/test_preallocate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
"""Preallocate tokens to investors.
Run MetalPay-like crowdsale.
"""
import datetime

import pytest
from eth_utils import to_wei
from web3.contract import Contract

from ico.tests.utils import time_travel
from ico.state import CrowdsaleState


@pytest.fixture
def token(chain, team_multisig):
"""Create a token with the initial supply."""

amount = 66588888
decimals = 8

args = ["Cowbits", "COW", amount * 10**decimals, decimals] # Owner set

tx = {
"from": team_multisig
}

contract, hash = chain.provider.deploy_contract('BurnableCrowdsaleToken', deploy_args=args, deploy_transaction=tx)
return contract


@pytest.fixture
def start_time() -> int:
"""Start Apr 15th"""
return int((datetime.datetime(2017, 4, 15, 16, 00) - datetime.datetime(1970, 1, 1)).total_seconds())


@pytest.fixture
def end_time(start_time) -> int:
"""Run 4 weeks."""
return start_time + 4 * 7 * 24 * 3600


@pytest.fixture
def minimum_funding_goal() -> int:
"""What is our minimum funding goal."""
return to_wei(7500, "ether")


@pytest.fixture
def founder_allocation() -> float:
"""How much tokens are allocated to founders, etc."""
return 0.2


@pytest.fixture
def pricing_strategy(chain, start_time, end_time):
args = [1] # 1 token = 1 eth
contract, hash = chain.provider.deploy_contract('FlatPricing', deploy_args=args)
return contract


@pytest.fixture
def early_investor_pool(accounts):
"""A pool where early investor tokens are collected"""
return accounts[9]



@pytest.fixture
def crowdsale(chain, team_multisig, start_time, end_time, pricing_strategy, token, early_investor_pool) -> Contract:
"""Create a crowdsale contract that sells from approve() pool."""

args = [
token.address,
pricing_strategy.address,
team_multisig,
start_time,
end_time,
0,
team_multisig,
]

tx = {
"from": team_multisig,
}

contract, hash = chain.provider.deploy_contract('AllocatedCrowdsale', deploy_args=args, deploy_transaction=tx)

assert contract.call().owner() == team_multisig
assert not token.call().released()

multiplier = 10**8

# Allow crowdsale contract to sell its token
# 30,500,000 is the sellable tokens in our case
token.transact({"from": team_multisig}).approve(contract.address, 33588888*multiplier)
token.transact({"from": team_multisig}).setTransferAgent(team_multisig, True)

# Presell tokens, almost all are sold out
assert contract.transact({"from": team_multisig}).preallocate(early_investor_pool, 3088888, 0)
assert contract.transact({"from": team_multisig}).preallocate(early_investor_pool, 5500000, 1)
assert contract.transact({"from": team_multisig}).preallocate(early_investor_pool, 5500000, 2)
assert contract.transact({"from": team_multisig}).preallocate(early_investor_pool, 5500000, 3)
assert contract.transact({"from": team_multisig}).preallocate(early_investor_pool, 14000000 - 100000, 4)

return contract


@pytest.fixture()
def finalizer(chain, token, crowdsale, team_multisig, founder_allocation) -> Contract:

# Create finalizer contract
args = [
token.address,
crowdsale.address,
]
contract, hash = chain.provider.deploy_contract('DefaultFinalizeAgent', deploy_args=args)

# Set crowdsale finalizer

# Allow finalzier to do mint()
token.transact({"from": team_multisig}).setReleaseAgent(contract.address)
crowdsale.transact({"from": team_multisig}).setFinalizeAgent(contract.address)
return contract


def test_buy_some(chain, crowdsale, token, finalizer, start_time, end_time, team_multisig, customer, founder_allocation):
"""Buy some token and finalize crowdsale."""

# Buy on first week
time_travel(chain, start_time + 1)
assert crowdsale.call().getState() == CrowdsaleState.Funding
initial_sold = crowdsale.call().tokensSold()

# Buy minimum funding goal
wei_value = 1000
crowdsale.transact({"from": customer, "value": wei_value}).buy()
assert crowdsale.call().isMinimumGoalReached()

# Close the deal
time_travel(chain, end_time + 1)
assert crowdsale.call().getState() == CrowdsaleState.Success
crowdsale.transact({"from": team_multisig}).finalize()
assert crowdsale.call().getState() == CrowdsaleState.Finalized

customer_tokens = 1000 * 10**8

# See that bounty tokens do not count against tokens sold
assert crowdsale.call().tokensSold() == customer_tokens + initial_sold

# See that customers get their tokens
assert token.call().balanceOf(customer) == customer_tokens

# Token is transferable
assert token.call().released()


def test_buy_all(chain, crowdsale, token, finalizer, start_time, end_time, team_multisig, customer, early_investor_pool):
"""Buy all tokens and finalize crowdsale."""

multiplier = 10**8
assert crowdsale.call().getTokensLeft() == 100000 * multiplier
assert token.call().balanceOf(early_investor_pool) == (33588888 - 100000) * multiplier
assert crowdsale.call().weiRaised() == 8860000000000000
assert crowdsale.call().tokensSold() == (33588888 - 100000) * multiplier

# Buy on first week
time_travel(chain, start_time + 1)
assert crowdsale.call().getState() == CrowdsaleState.Funding

# Buy all cap
wei_value = int(crowdsale.call().getTokensLeft() / 10**8)
crowdsale.transact({"from": customer, "value": wei_value}).buy()
assert crowdsale.call().isCrowdsaleFull()

# Close the deal
time_travel(chain, end_time + 1)
assert crowdsale.call().getState() == CrowdsaleState.Success
crowdsale.transact({"from": team_multisig}).finalize()
assert crowdsale.call().getState() == CrowdsaleState.Finalized

0 comments on commit 3f2c2bc

Please sign in to comment.