-
Notifications
You must be signed in to change notification settings - Fork 559
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
325 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
pragma solidity ^0.4.6; | ||
|
||
import "./PricingStrategy.sol"; | ||
|
||
|
||
/** | ||
* Time milestone based pricing with special support for pre-ico deals. | ||
*/ | ||
contract MilestonePricing is PricingStrategy { | ||
|
||
uint public constant MAX_MILESTONE = 10; | ||
|
||
// This is our PresaleFundCollector contract | ||
address public preicoContractAddress; | ||
|
||
// Price for presale investors weis per toke | ||
uint public preicoPrice; | ||
|
||
/** | ||
* Define pricing schedule using milestones. | ||
*/ | ||
struct Milestone { | ||
|
||
// UNIX timestamp when this milestone kicks in | ||
uint time; | ||
|
||
// How many tokens per satoshi you will get after this milestone has been passed | ||
uint price; | ||
} | ||
|
||
// Store milestones in a fixed array, so that it can be seen in a blockchain explorer | ||
// Milestone 0 is always (0, 0) | ||
// (TODO: change this when we confirm dynamic arrays are explorable) | ||
Milestone[10] public milestones; | ||
|
||
// How many active milestones we have | ||
uint public milestoneCount; | ||
|
||
/** | ||
* @param _preicoContractAddress PresaleFundCollector address | ||
* @param _preicoPrice How many weis one token cost for pre-ico investors | ||
* @param _milestones uint[] miletones Pairs of (time, price) | ||
*/ | ||
function MilestonePricing(address _preicoContractAddress, uint _preicoPrice, uint[] _milestones) { | ||
|
||
preicoContractAddress = _preicoContractAddress; | ||
preicoPrice = _preicoPrice; | ||
|
||
// Need to have tuples, length check | ||
if(_milestones.length % 2 == 1 || _milestones.length >= MAX_MILESTONE) { | ||
throw; | ||
} | ||
|
||
milestoneCount = _milestones.length / 2; | ||
|
||
for(uint i=0; i<_milestones.length/2; i++) { | ||
milestones[i].time = _milestones[i*2]; | ||
milestones[i].price = _milestones[i*2+1]; | ||
} | ||
} | ||
|
||
/** | ||
* Iterate through milestones. | ||
* | ||
* You reach end of milestones when price = 0 | ||
* | ||
* @return tuple (time, price) | ||
*/ | ||
function getMilestone(uint n) public constant returns (uint, uint) { | ||
return (milestones[n].time, milestones[n].price); | ||
} | ||
|
||
/** | ||
* Get the current milestone or bail out if we are not in the milestone periods. | ||
* | ||
* @return {[type]} [description] | ||
*/ | ||
function getCurrentMilestone() private constant returns (Milestone) { | ||
uint i; | ||
uint price; | ||
|
||
for(i=0; i<milestones.length; i++) { | ||
if(now < milestones[i].time) { | ||
return milestones[i-1]; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Get the current price. | ||
* | ||
* @return The current price or 0 if we are outside milestone period | ||
*/ | ||
function getCurrentPrice() public constant returns (uint result) { | ||
return getCurrentMilestone().price; | ||
} | ||
|
||
/** | ||
* Calculate the current price for buy in amount. | ||
*/ | ||
function calculatePrice(uint value, uint tokensSold, uint weiRaised, address msgSender) public constant returns (uint) { | ||
|
||
// This investor is coming through pre-ico | ||
if(msgSender == preicoContractAddress) { | ||
return value / preicoPrice; | ||
} | ||
|
||
uint price = getCurrentPrice(); | ||
return value / price; | ||
} | ||
|
||
function() payable { | ||
throw; // No money on this contract | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
"""Milestone based pricing""" | ||
import datetime | ||
|
||
import pytest | ||
from eth_utils import to_wei | ||
from ethereum.tester import TransactionFailed | ||
from web3.contract import Contract | ||
|
||
|
||
from ico.tests.utils import time_travel | ||
|
||
|
||
@pytest.fixture | ||
def presale_fund_collector(chain, presale_freeze_ends_at, team_multisig) -> Contract: | ||
"""In actual ICO, the price is doubled (for testing purposes).""" | ||
|
||
args = [ | ||
team_multisig, | ||
presale_freeze_ends_at, | ||
to_wei("50", "ether"), # Minimum presale buy in is 50 ethers | ||
|
||
] | ||
tx = { | ||
"from": team_multisig, | ||
} | ||
presale_fund_collector, hash = chain.provider.deploy_contract('PresaleFundCollector', deploy_args=args, deploy_transaction=tx) | ||
return presale_fund_collector | ||
|
||
|
||
@pytest.fixture | ||
def start_time() -> int: | ||
return int((datetime.datetime(2017, 4, 15, 16, 00) - datetime.datetime(1970, 1, 1)).total_seconds()) | ||
|
||
|
||
@pytest.fixture | ||
def token(uncapped_token) -> int: | ||
"""Token contract used in milestone tests""" | ||
return uncapped_token | ||
|
||
|
||
@pytest.fixture | ||
def milestone_pricing(chain, presale_fund_collector, start_time): | ||
|
||
week = 24*3600 * 7 | ||
|
||
args = [ | ||
presale_fund_collector.address, | ||
to_wei("0.05", "ether"), | ||
[ | ||
start_time + 0, to_wei("0.10", "ether"), | ||
start_time + week*1, to_wei("0.12", "ether"), | ||
start_time + week*2, to_wei("0.13", "ether"), | ||
start_time + week*4, to_wei("0.13", "ether"), | ||
], | ||
] | ||
|
||
tx = { | ||
"gas": 4000000 | ||
} | ||
contract, hash = chain.provider.deploy_contract('MilestonePricing', deploy_args=args, deploy_transaction=tx) | ||
return contract | ||
|
||
|
||
@pytest.fixture | ||
def milestone_ico(chain, beneficiary, team_multisig, start_time, milestone_pricing, preico_cap, preico_funding_goal, token, presale_fund_collector) -> Contract: | ||
"""Create a crowdsale contract that uses milestone based pricing.""" | ||
|
||
ends_at = start_time + 4*24*3600 | ||
|
||
args = [ | ||
token.address, | ||
milestone_pricing.address, | ||
team_multisig, | ||
beneficiary, | ||
start_time, | ||
ends_at, | ||
0, | ||
] | ||
|
||
tx = { | ||
"from": team_multisig, | ||
} | ||
|
||
contract, hash = chain.provider.deploy_contract('UncappedCrowdsale', deploy_args=args, deploy_transaction=tx) | ||
|
||
assert contract.call().owner() == team_multisig | ||
assert not token.call().released() | ||
|
||
# Allow pre-ico contract to do mint() | ||
token.transact({"from": team_multisig}).setMintAgent(contract.address, True) | ||
assert token.call().mintAgents(contract.address) == True | ||
|
||
return contract | ||
|
||
|
||
def test_milestone_getter(chain, milestone_pricing, start_time): | ||
"""Milestone data is exposed to the world.""" | ||
|
||
time, price = milestone_pricing.call().getMilestone(0) | ||
assert time == 1492272000 | ||
assert price == 100000000000000000 | ||
|
||
|
||
def test_milestone_prices(chain, milestone_pricing, start_time, customer): | ||
"""We get correct milestone prices for different dates.""" | ||
|
||
time_travel(chain, start_time - 1) | ||
with pytest.raises(TransactionFailed): | ||
# Div by zero, crowdsale has not begin yet | ||
assert milestone_pricing.call().getCurrentPrice() | ||
|
||
time_travel(chain, start_time) | ||
assert milestone_pricing.call().getCurrentPrice() == to_wei("0.10", "ether") | ||
|
||
time_travel(chain, start_time + 1) | ||
assert milestone_pricing.call().getCurrentPrice() == to_wei("0.10", "ether") | ||
|
||
# 1 week forward | ||
time_travel(chain, int((datetime.datetime(2017, 4, 22, 16, 0) - datetime.datetime(1970, 1, 1)).total_seconds())) | ||
assert milestone_pricing.call().getCurrentPrice() == to_wei("0.12", "ether") | ||
|
||
# 2 week forward | ||
time_travel(chain, int((datetime.datetime(2017, 4, 29, 16, 0) - datetime.datetime(1970, 1, 1)).total_seconds())) | ||
assert milestone_pricing.call().getCurrentPrice() == to_wei("0.13", "ether") | ||
|
||
# See that we divide price correctly | ||
assert milestone_pricing.call().calculatePrice( | ||
to_wei("0.26", "ether"), | ||
0, | ||
0, | ||
customer | ||
) == 2 | ||
|
||
|
||
def test_milestone_calculate_preico_price(chain, milestone_pricing, start_time, presale_fund_collector): | ||
"""Preico contributors get their special price.""" | ||
|
||
# 1 week forward | ||
time_travel(chain, int((datetime.datetime(2017, 4, 22, 16, 0) - datetime.datetime(1970, 1, 1)).total_seconds())) | ||
|
||
# Pre-ico address always buys at the fixed price | ||
assert milestone_pricing.call().calculatePrice( | ||
to_wei("0.05", "ether"), | ||
0, | ||
0, | ||
presale_fund_collector.address | ||
) == 1 | ||
|
||
|
||
def test_presale_move_to_milestone_crowdsale(chain, presale_fund_collector, milestone_ico, token, start_time, team_multisig, customer, customer_2): | ||
"""When pre-ico contract funds are moved to the crowdsale, the pre-sale investors gets tokens with a preferred price and not the current milestone price.""" | ||
|
||
value = to_wei(50, "ether") | ||
presale_fund_collector.transact({"from": customer, "value": value}).invest() | ||
|
||
# ICO begins, Link presale to an actual ICO | ||
presale_fund_collector.transact({"from": team_multisig}).setCrowdsale(milestone_ico.address) | ||
time_travel(chain, start_time) | ||
|
||
# Load funds to ICO | ||
presale_fund_collector.transact().parcipateCrowdsaleAll() | ||
|
||
# Tokens received, paid by preico price | ||
milestone_ico.call().investedAmountOf(customer) == to_wei(50, "ether") | ||
token.call().balanceOf(customer) == 50 / 0.050 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.