├── .editorconfig ├── .gitignore ├── .gitmodules ├── .travis.yml ├── README.md ├── build └── expanded │ └── README.txt ├── contracts ├── Crowdsale.sol ├── CrowdsaleToken.sol ├── FinalizeAgent.sol ├── FractionalERC20.sol ├── GnosisWallet.sol ├── Haltable.sol ├── IntermediateVault.sol ├── MintableToken.sol ├── MultiVault.sol ├── MysteriumCrowdsale.sol ├── MysteriumPricing.sol ├── MysteriumToken.sol ├── MysteriumTokenDistribution.sol ├── PricingStrategy.sol ├── ReleasableToken.sol ├── SafeMathLib.sol ├── StandardToken.sol ├── UpgradeAgent.sol ├── UpgradeableToken.sol └── test │ ├── SimpleReleaseAgent.sol │ └── TestMigrationTarget.sol ├── crowdsales ├── README.txt ├── import-investors.py ├── mysterium-kovan.yml ├── mysterium-mainnet.yml ├── mysterium-new-finalizeagent.deployment-report.yml ├── mysterium-testrpc.yml ├── mysterium-vaults-kovan.deployment-report.yml ├── mysterium-vaults-kovan.yml ├── mysterium-vaults-mainnet.deployment-report.yml └── mysterium-vaults-mainnet.yml ├── fake_seed_investor_data.csv ├── founders_data.csv ├── helpers ├── before_crowdsale.py └── coins_calculator.py ├── install_solc.sh ├── populus.json ├── pytest.ini ├── requirements.txt ├── seed_investor_data.csv ├── tests ├── conftest.py ├── fixtures │ ├── finalize.py │ ├── flatprice.py │ ├── general.py │ ├── presale.py │ └── releasable.py ├── test_crowdsale.py ├── test_deploy_all.py ├── test_intermediate_vault.py ├── test_multivault.py ├── test_mysterium_token.py ├── test_set_chf.py └── utils.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.sol] 14 | indent_style = space 15 | indent_size = 2 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | charset = utf-8 19 | end_of_line = lf 20 | 21 | [*.bat] 22 | indent_style = tab 23 | end_of_line = crlf 24 | 25 | [LICENSE] 26 | insert_final_newline = false 27 | 28 | [Makefile] 29 | indent_style = tab 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # pyenv python configuration file 62 | .python-version 63 | 64 | chains/* 65 | eireg/chains/* 66 | venv 67 | build 68 | 69 | # Where Populus has deployed contracts 70 | registrar.json 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | *.ipynb 75 | 76 | # Crowdsale definitions 77 | #crowdsales/*.yml 78 | #*.csv 79 | 80 | contracts/PreICOProxyBuyer.sol 81 | tests 82 | tests/test_preico_proxy_buy.py 83 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zeppelin"] 2 | path = zeppelin 3 | url = https://github.com/OpenZeppelin/zeppelin-solidity.git 4 | [submodule "ico"] 5 | path = ico 6 | url = git@github.com:TokenMarketNet/ico.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3.5 3 | dist: trusty 4 | sudo: true 5 | 6 | env: 7 | - TOXENV=py35 SOLC_VERSION=0.4.8 8 | 9 | cache: 10 | - pip: true 11 | - directories: 12 | # - $TRAVIS_BUILD_DIR/solc-versions 13 | - $TRAVIS_BUILD_DIR/.tox --recreate 14 | 15 | before_install: 16 | - if [ -n "$SOLC_VERSION" ]; then export SOLC_BINARY="$TRAVIS_BUILD_DIR/solc-versions/solc-$SOLC_VERSION/solc"; fi 17 | - if [ -n "$SOLC_VERSION" ]; then export LD_LIBRARY_PATH="$TRAVIS_BUILD_DIR/solc-versions/solc-$SOLC_VERSION"; fi 18 | - if [ -n "$SOLC_VERSION" ]; then sudo apt-get install -y tree unzip; fi 19 | - sudo add-apt-repository -y ppa:ethereum/ethereum 20 | - sudo apt-get update 21 | 22 | install: 23 | - if [ ! -e "$SOLC_BINARY" ]; then /.$TRAVIS_BUILD_DIR/install_solc.sh; fi 24 | - travis_retry pip install setuptools --upgrade 25 | - pip install -U tox 26 | 27 | before_script: 28 | - if [ -n "$SOLC_BINARY" ]; then $SOLC_BINARY --version; fi 29 | - export PATH=$PATH:`dirname $SOLC_BINARY` 30 | 31 | # command to run tests, e.g. python setup.py test 32 | script: tox -e ${TOXENV} 33 | 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Mysterium token and crowdsale features 4 | 5 | * Zeppelin StandardToken with upgradeable trait (Golem like) and releaseable (owner can decide when tokens are transferred) 6 | 7 | * Tokens are minted during the crowdsale (MysteriumCrowdsale.assignTokens) 8 | 9 | * Extra tokens for founders, seed round, etc. are minted after the crowdsale is over (MysteriumTokenDistribution.distribute) 10 | 11 | * Crowdsale priced in CHF (MysteriumPricing.setConversionRate) 12 | 13 | * Pricing has soft and hard cap (MysteriumPricing.calculatePrice) 14 | 15 | * Reaching soft cap triggers 72 hours closing time (MysteriumCrowdsale.triggerSoftCap) 16 | 17 | * Crowdsale can whitelist early participants (Crowdsale.setEarlyParicipantWhitelist) 18 | 19 | * Tokens are deposited to time locked vaults (MultiVault) 20 | 21 | * Team funds are transferred through a 30 days delay vault (IntermediateVault) 22 | 23 | * The crowdsale can be stopped in emergency (Haltable) 24 | 25 | ## Installation 26 | 27 | OSX or Linux required. 28 | 29 | [Install solc 0.4.8](http://solidity.readthedocs.io/en/develop/installing-solidity.html#binary-packages). This exact version is required. Read full paragraph how to install it on OSX. 30 | 31 | Install Populus in Python virtual environment. 32 | 33 | Clone the repository and initialize submodules: 34 | 35 | git clone --recursive git@github.com:MysteriumNetwork/contracts.git 36 | 37 | First Install Python 3.5. Then in the repo folder: 38 | 39 | cd contracts 40 | python3.5 -m venv venv 41 | source venv/bin/activate 42 | pip install -r requirements.txt 43 | pip install -e ico 44 | 45 | Then test solc: 46 | 47 | solc --version 48 | 49 | solc, the solidity compiler commandline interface 50 | Version: 0.4.8+commit.60cc1668.Darwin.appleclang 51 | 52 | Then test populus: 53 | 54 | populus 55 | 56 | Usage: populus [OPTIONS] COMMAND [ARGS]... 57 | ... 58 | 59 | ## Compiling contracts 60 | 61 | Compile: 62 | 63 | populus compile 64 | 65 | Output will be in `build` folder. 66 | 67 | ## Running tests 68 | 69 | Tests are written using `py.test` in tests folder. 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /build/expanded/README.txt: -------------------------------------------------------------------------------- 1 | Placeholder for deployed contract files on EtherScan verification 2 | -------------------------------------------------------------------------------- /contracts/Crowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "./SafeMathLib.sol"; 4 | import "./Haltable.sol"; 5 | import "./PricingStrategy.sol"; 6 | import "./FinalizeAgent.sol"; 7 | import "./FractionalERC20.sol"; 8 | 9 | 10 | /** 11 | * Abstract base contract for token sales. 12 | * 13 | * Handle 14 | * - start and end dates 15 | * - accepting investments 16 | * - minimum funding goal and refund 17 | * - various statistics during the crowdfund 18 | * - different pricing strategies 19 | * - different investment policies (require server side customer id, allow only whitelisted addresses) 20 | * 21 | */ 22 | contract Crowdsale is Haltable { 23 | 24 | using SafeMathLib for uint; 25 | 26 | /* The token we are selling */ 27 | FractionalERC20 public token; 28 | 29 | /* How we are going to price our offering */ 30 | PricingStrategy public pricingStrategy; 31 | 32 | /* Post-success callback */ 33 | FinalizeAgent public finalizeAgent; 34 | 35 | /* tokens will be transfered from this address */ 36 | address public multisigWallet; 37 | 38 | /* if the funding goal is not reached, investors may withdraw their funds */ 39 | uint public minimumFundingGoal; 40 | 41 | /* the UNIX timestamp start date of the crowdsale */ 42 | uint public startsAt; 43 | 44 | /* the UNIX timestamp end date of the crowdsale */ 45 | uint public endsAt; 46 | 47 | /* the number of tokens already sold through this contract*/ 48 | uint public tokensSold = 0; 49 | 50 | /* How many wei of funding we have raised */ 51 | uint public weiRaised = 0; 52 | 53 | /* How many distinct addresses have invested */ 54 | uint public investorCount = 0; 55 | 56 | /* How much wei we have returned back to the contract after a failed crowdfund. */ 57 | uint public loadedRefund = 0; 58 | 59 | /* How much wei we have given back to investors.*/ 60 | uint public weiRefunded = 0; 61 | 62 | /* Has this crowdsale been finalized */ 63 | bool public finalized; 64 | 65 | /* Do we need to have unique contributor id for each customer */ 66 | bool public requireCustomerId; 67 | 68 | /** 69 | * Do we verify that contributor has been cleared on the server side (accredited investors only). 70 | * This method was first used in FirstBlood crowdsale to ensure all contributors have accepted terms on sale (on the web). 71 | */ 72 | bool public requiredSignedAddress; 73 | 74 | /* Server side address that signed allowed contributors (Ethereum addresses) that can participate the crowdsale */ 75 | address public signerAddress; 76 | 77 | /** How much ETH each address has invested to this crowdsale */ 78 | mapping (address => uint256) public investedAmountOf; 79 | 80 | /** How much tokens this crowdsale has credited for each investor address */ 81 | mapping (address => uint256) public tokenAmountOf; 82 | 83 | /** Addresses that are allowed to invest even before ICO offical opens. For testing, for ICO partners, etc. */ 84 | mapping (address => bool) public earlyParticipantWhitelist; 85 | 86 | /** This is for manul testing for the interaction from owner wallet. You can set it to any value and inspect this in blockchain explorer to see that crowdsale interaction works. */ 87 | uint public ownerTestValue; 88 | 89 | /** State machine 90 | * 91 | * - Preparing: All contract initialization calls and variables have not been set yet 92 | * - Prefunding: We have not passed start time yet 93 | * - Funding: Active crowdsale 94 | * - Success: Minimum funding goal reached 95 | * - Failure: Minimum funding goal not reached before ending time 96 | * - Finalized: The finalized has been called and succesfully executed 97 | * - Refunding: Refunds are loaded on the contract for reclaim. 98 | */ 99 | enum State{Unknown, Preparing, PreFunding, Funding, Success, Failure, Finalized, Refunding} 100 | 101 | // A new investment was made 102 | event Invested(address investor, uint weiAmount, uint tokenAmount, uint128 customerId); 103 | 104 | // Refund was processed for a contributor 105 | event Refund(address investor, uint weiAmount); 106 | 107 | // The rules were changed what kind of investments we accept 108 | event InvestmentPolicyChanged(bool requireCustomerId, bool requiredSignedAddress, address signerAddress); 109 | 110 | // Address early participation whitelist status changed 111 | event Whitelisted(address addr, bool status); 112 | 113 | // Crowdsale end time has been changed 114 | event EndsAtChanged(uint endsAt); 115 | 116 | function Crowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal) { 117 | 118 | if(_minimumFundingGoal != 0) { 119 | // Mysterium specific fix to allow funding goal only be set in CHF 120 | } 121 | 122 | owner = msg.sender; 123 | 124 | token = FractionalERC20(_token); 125 | 126 | setPricingStrategy(_pricingStrategy); 127 | 128 | multisigWallet = _multisigWallet; 129 | if(multisigWallet == 0) { 130 | throw; 131 | } 132 | 133 | if(_start == 0) { 134 | throw; 135 | } 136 | 137 | startsAt = _start; 138 | 139 | if(_end == 0) { 140 | throw; 141 | } 142 | 143 | endsAt = _end; 144 | 145 | // Don't mess the dates 146 | if(startsAt >= endsAt) { 147 | throw; 148 | } 149 | 150 | } 151 | 152 | /** 153 | * Don't expect to just send in money and get tokens. 154 | */ 155 | function() payable { 156 | throw; 157 | } 158 | 159 | /** 160 | * Make an investment. 161 | * 162 | * Crowdsale must be running for one to invest. 163 | * We must have not pressed the emergency brake. 164 | * 165 | * @param receiver The Ethereum address who receives the tokens 166 | * @param customerId (optional) UUID v4 to track the successful payments on the server side 167 | * 168 | */ 169 | function investInternal(address receiver, uint128 customerId) stopInEmergency private { 170 | 171 | // Determine if it's a good time to accept investment from this participant 172 | if(getState() == State.PreFunding) { 173 | // Are we whitelisted for early deposit 174 | if(!earlyParticipantWhitelist[receiver]) { 175 | throw; 176 | } 177 | } else if(getState() == State.Funding) { 178 | // Retail participants can only come in when the crowdsale is running 179 | // pass 180 | } else { 181 | // Unwanted state 182 | throw; 183 | } 184 | 185 | uint weiAmount = msg.value; 186 | uint tokenAmount = pricingStrategy.calculatePrice(weiAmount, weiRaised, tokensSold, msg.sender, token.decimals()); 187 | 188 | if(tokenAmount == 0) { 189 | // Dust transaction 190 | throw; 191 | } 192 | 193 | if(investedAmountOf[receiver] == 0) { 194 | // A new investor 195 | investorCount++; 196 | } 197 | 198 | // Update investor 199 | investedAmountOf[receiver] = investedAmountOf[receiver].plus(weiAmount); 200 | tokenAmountOf[receiver] = tokenAmountOf[receiver].plus(tokenAmount); 201 | 202 | // Update totals 203 | weiRaised = weiRaised.plus(weiAmount); 204 | tokensSold = tokensSold.plus(tokenAmount); 205 | 206 | // Check that we did not bust the cap 207 | if(isBreakingCap(tokenAmount, weiAmount, weiRaised, tokensSold)) { 208 | throw; 209 | } 210 | 211 | assignTokens(receiver, tokenAmount); 212 | 213 | // Pocket the money 214 | if(!multisigWallet.send(weiAmount)) throw; 215 | 216 | // Tell us invest was success 217 | Invested(receiver, weiAmount, tokenAmount, customerId); 218 | 219 | // Call the invest hooks 220 | onInvest(); 221 | } 222 | 223 | /** 224 | * Track who is the customer making the payment so we can send thank you email. 225 | */ 226 | function investWithCustomerId(address addr, uint128 customerId) public payable { 227 | if(requiredSignedAddress) throw; // Crowdsale allows only server-side signed participants 228 | if(customerId == 0) throw; // UUIDv4 sanity check 229 | investInternal(addr, customerId); 230 | } 231 | 232 | /** 233 | * Allow anonymous contributions to this crowdsale. 234 | */ 235 | function invest(address addr) public payable { 236 | if(requireCustomerId) throw; // Crowdsale needs to track partipants for thank you email 237 | if(requiredSignedAddress) throw; // Crowdsale allows only server-side signed participants 238 | investInternal(addr, 0); 239 | } 240 | 241 | /** 242 | * Invest to tokens, recognize the payer. 243 | * 244 | */ 245 | function buyWithCustomerId(uint128 customerId) public payable { 246 | investWithCustomerId(msg.sender, customerId); 247 | } 248 | 249 | /** 250 | * The basic entry point to participate the crowdsale process. 251 | * 252 | * Pay for funding, get invested tokens back in the sender address. 253 | */ 254 | function buy() public payable { 255 | invest(msg.sender); 256 | } 257 | 258 | /** 259 | * Finalize a succcesful crowdsale. 260 | * 261 | * The owner can triggre a call the contract that provides post-crowdsale actions, like releasing the tokens. 262 | */ 263 | function finalize() public inState(State.Success) onlyOwner stopInEmergency { 264 | 265 | // Already finalized 266 | if(finalized) { 267 | throw; 268 | } 269 | 270 | // Finalizing is optional. We only call it if we are given a finalizing agent. 271 | if(address(finalizeAgent) != 0) { 272 | finalizeAgent.finalizeCrowdsale(); 273 | } 274 | 275 | finalized = true; 276 | } 277 | 278 | /** 279 | * Allow to (re)set finalize agent. 280 | * 281 | * Design choice: no state restrictions on setting this, so that we can fix fat finger mistakes. 282 | */ 283 | function setFinalizeAgent(FinalizeAgent addr) onlyOwner { 284 | finalizeAgent = addr; 285 | 286 | // Don't allow setting bad agent 287 | if(!finalizeAgent.isFinalizeAgent()) { 288 | throw; 289 | } 290 | } 291 | 292 | /** 293 | * Set policy do we need to have server-side customer ids for the investments. 294 | * 295 | */ 296 | function setRequireCustomerId(bool value) onlyOwner { 297 | requireCustomerId = value; 298 | InvestmentPolicyChanged(requireCustomerId, requiredSignedAddress, signerAddress); 299 | } 300 | 301 | /** 302 | * Allow addresses to do early participation. 303 | * 304 | * TODO: Fix spelling error in the name 305 | */ 306 | function setEarlyParicipantWhitelist(address addr, bool status) onlyOwner { 307 | earlyParticipantWhitelist[addr] = status; 308 | Whitelisted(addr, status); 309 | } 310 | 311 | /** 312 | * Allow to (re)set pricing strategy. 313 | * 314 | * Design choice: no state restrictions on the set, so that we can fix fat finger mistakes. 315 | */ 316 | function setPricingStrategy(PricingStrategy _pricingStrategy) onlyOwner { 317 | pricingStrategy = _pricingStrategy; 318 | 319 | // Don't allow setting bad agent 320 | if(!pricingStrategy.isPricingStrategy()) { 321 | throw; 322 | } 323 | } 324 | 325 | /** 326 | * Allow load refunds back on the contract for the refunding. 327 | * 328 | * The team can transfer the funds back on the smart contract in the case the minimum goal was not reached.. 329 | */ 330 | function loadRefund() public payable inState(State.Failure) { 331 | if(msg.value == 0) throw; 332 | loadedRefund = loadedRefund.plus(msg.value); 333 | } 334 | 335 | /** 336 | * Investors can claim refund. 337 | */ 338 | function refund() public inState(State.Refunding) { 339 | uint256 weiValue = investedAmountOf[msg.sender]; 340 | if (weiValue == 0) throw; 341 | investedAmountOf[msg.sender] = 0; 342 | weiRefunded = weiRefunded.plus(weiValue); 343 | Refund(msg.sender, weiValue); 344 | if (!msg.sender.send(weiValue)) throw; 345 | } 346 | 347 | /** 348 | * @return true if the crowdsale has raised enough money to be a succes 349 | */ 350 | function isMinimumGoalReached() public constant returns (bool reached) { 351 | return weiRaised >= minimumFundingGoal; 352 | } 353 | 354 | /** 355 | * Check if the contract relationship looks good. 356 | */ 357 | function isFinalizerSane() public constant returns (bool sane) { 358 | return finalizeAgent.isSane(); 359 | } 360 | 361 | /** 362 | * Check if the contract relationship looks good. 363 | */ 364 | function isPricingSane() public constant returns (bool sane) { 365 | return pricingStrategy.isSane(address(this)); 366 | } 367 | 368 | /** 369 | * Crowdfund state machine management. 370 | * 371 | * We make it a function and do not assign the result to a variable, so there is no chance of the variable being stale. 372 | */ 373 | function getState() public constant returns (State) { 374 | if(finalized) return State.Finalized; 375 | else if (address(finalizeAgent) == 0) return State.Preparing; 376 | else if (!finalizeAgent.isSane()) return State.Preparing; 377 | else if (!pricingStrategy.isSane(address(this))) return State.Preparing; 378 | else if (block.timestamp < startsAt) return State.PreFunding; 379 | else if (block.timestamp <= endsAt && !isCrowdsaleFull()) return State.Funding; 380 | else if (isMinimumGoalReached()) return State.Success; 381 | else if (!isMinimumGoalReached() && weiRaised > 0 && loadedRefund >= weiRaised) return State.Refunding; 382 | else return State.Failure; 383 | } 384 | 385 | /** This is for manual testing of multisig wallet interaction */ 386 | function setOwnerTestValue(uint val) onlyOwner { 387 | ownerTestValue = val; 388 | } 389 | 390 | /** Interface marker. */ 391 | function isCrowdsale() public constant returns (bool) { 392 | return true; 393 | } 394 | 395 | 396 | /** 397 | * Allow subcontracts to take extra actions on a successful invet. 398 | */ 399 | function onInvest() internal { 400 | 401 | } 402 | 403 | // 404 | // Modifiers 405 | // 406 | 407 | /** Modified allowing execution only if the crowdsale is currently running. */ 408 | modifier inState(State state) { 409 | if(getState() != state) throw; 410 | _; 411 | } 412 | 413 | /** 414 | * Allow crowdsale owner to close early or extend the crowdsale. 415 | * 416 | * This is useful e.g. for a manual soft cap implementation: 417 | * - after X amount is reached determine manual closing 418 | * 419 | * This may put the crowdsale to an invalid state, 420 | * but we trust owners know what they are doing. 421 | * 422 | */ 423 | function setEndsAt(uint time) onlyOwner { 424 | 425 | if(now > time) { 426 | throw; // Don't change past 427 | } 428 | 429 | endsAt = time; 430 | EndsAtChanged(endsAt); 431 | } 432 | 433 | // 434 | // Abstract functions 435 | // 436 | 437 | /** 438 | * Check if the current invested breaks our cap rules. 439 | * 440 | * 441 | * The child contract must define their own cap setting rules. 442 | * We allow a lot of flexibility through different capping strategies (ETH, token count) 443 | * Called from invest(). 444 | * 445 | * @param weiAmount The amount of wei the investor tries to invest in the current transaction 446 | * @param tokenAmount The amount of tokens we try to give to the investor in the current transaction 447 | * @param weiRaisedTotal What would be our total raised balance after this transaction 448 | * @param tokensSoldTotal What would be our total sold tokens count after this transaction 449 | * 450 | * @return true if taking this investment would break our cap rules 451 | */ 452 | function isBreakingCap(uint weiAmount, uint tokenAmount, uint weiRaisedTotal, uint tokensSoldTotal) constant returns (bool limitBroken); 453 | 454 | /** 455 | * Check if the current crowdsale is full and we can no longer sell any tokens. 456 | */ 457 | function isCrowdsaleFull() public constant returns (bool); 458 | 459 | /** 460 | * Create new tokens or transfer issued tokens to the investor depending on the cap model. 461 | */ 462 | function assignTokens(address receiver, uint tokenAmount) private; 463 | } 464 | -------------------------------------------------------------------------------- /contracts/CrowdsaleToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "./StandardToken.sol"; 4 | import "./UpgradeableToken.sol"; 5 | import "./ReleasableToken.sol"; 6 | import "./MintableToken.sol"; 7 | import "./SafeMathLib.sol"; 8 | 9 | 10 | /** 11 | * A crowdsaled token. 12 | * 13 | * An ERC-20 token designed specifically for crowdsales with investor protection and further development path. 14 | * 15 | * - The token transfer() is disabled until the crowdsale is over 16 | * - The token contract gives an opt-in upgrade path to a new contract 17 | * - The same token can be part of several crowdsales through approve() mechanism 18 | * - The token can be capped (supply set in the constructor) or uncapped (crowdsale contract can mint new tokens) 19 | * 20 | */ 21 | contract CrowdsaleToken is ReleasableToken, MintableToken, UpgradeableToken { 22 | 23 | event UpdatedTokenInformation(string newName, string newSymbol); 24 | 25 | string public name; 26 | 27 | string public symbol; 28 | 29 | uint public decimals; 30 | 31 | /** 32 | * Construct the token. 33 | * 34 | * This token must be created through a team multisig wallet, so that it is owned by that wallet. 35 | */ 36 | function CrowdsaleToken(string _name, string _symbol, uint _initialSupply, uint _decimals) 37 | UpgradeableToken(msg.sender) { 38 | 39 | // Create any address, can be transferred 40 | // to team multisig via changeOwner(), 41 | // also remember to call setUpgradeMaster() 42 | owner = msg.sender; 43 | 44 | name = _name; 45 | symbol = _symbol; 46 | 47 | totalSupply = _initialSupply; 48 | 49 | decimals = _decimals; 50 | 51 | // Create initially all balance on the team multisig 52 | balances[owner] = totalSupply; 53 | } 54 | 55 | /** 56 | * When token is released to be transferable, enforce no new tokens can be created. 57 | */ 58 | function releaseTokenTransfer() public onlyReleaseAgent { 59 | mintingFinished = true; 60 | super.releaseTokenTransfer(); 61 | } 62 | 63 | /** 64 | * Allow upgrade agent functionality kick in only if the crowdsale was success. 65 | */ 66 | function canUpgrade() public constant returns(bool) { 67 | return released; 68 | } 69 | 70 | /** 71 | * Owner can update token information here 72 | */ 73 | function setTokenInformation(string _name, string _symbol) onlyOwner { 74 | name = _name; 75 | symbol = _symbol; 76 | 77 | UpdatedTokenInformation(name, symbol); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /contracts/FinalizeAgent.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | /** 4 | * Finalize agent defines what happens at the end of succeseful crowdsale. 5 | * 6 | * - Allocate tokens for founders, bounties and community 7 | * - Make tokens transferable 8 | * - etc. 9 | */ 10 | contract FinalizeAgent { 11 | 12 | function isFinalizeAgent() public constant returns(bool) { 13 | return true; 14 | } 15 | 16 | /** Return true if we can run finalizeCrowdsale() properly. 17 | * 18 | * This is a safety check function that doesn't allow crowdsale to begin 19 | * unless the finalizer has been set up properly. 20 | */ 21 | function isSane() public constant returns (bool); 22 | 23 | /** Called once by crowdsale finalize() if the sale was success. */ 24 | function finalizeCrowdsale(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /contracts/FractionalERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "zeppelin/contracts/token/ERC20.sol"; 4 | 5 | /** 6 | * A token that defines fractional units as decimals. 7 | */ 8 | contract FractionalERC20 is ERC20 { 9 | 10 | uint public decimals; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /contracts/GnosisWallet.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * Originally from https://github.com/ConsenSys/MultiSigWallet 3 | */ 4 | 5 | pragma solidity 0.4.8; 6 | 7 | 8 | /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. 9 | /// @author Stefan George - 10 | contract MultiSigWallet { 11 | 12 | uint constant public MAX_OWNER_COUNT = 50; 13 | 14 | event Confirmation(address indexed sender, uint indexed transactionId); 15 | event Revocation(address indexed sender, uint indexed transactionId); 16 | event Submission(uint indexed transactionId); 17 | event Execution(uint indexed transactionId); 18 | event ExecutionFailure(uint indexed transactionId); 19 | event Deposit(address indexed sender, uint value); 20 | event OwnerAddition(address indexed owner); 21 | event OwnerRemoval(address indexed owner); 22 | event RequirementChange(uint required); 23 | 24 | mapping (uint => Transaction) public transactions; 25 | mapping (uint => mapping (address => bool)) public confirmations; 26 | mapping (address => bool) public isOwner; 27 | address[] public owners; 28 | uint public required; 29 | uint public transactionCount; 30 | 31 | struct Transaction { 32 | address destination; 33 | uint value; 34 | bytes data; 35 | bool executed; 36 | } 37 | 38 | modifier onlyWallet() { 39 | if (msg.sender != address(this)) 40 | throw; 41 | _; 42 | } 43 | 44 | modifier ownerDoesNotExist(address owner) { 45 | if (isOwner[owner]) 46 | throw; 47 | _; 48 | } 49 | 50 | modifier ownerExists(address owner) { 51 | if (!isOwner[owner]) 52 | throw; 53 | _; 54 | } 55 | 56 | modifier transactionExists(uint transactionId) { 57 | if (transactions[transactionId].destination == 0) 58 | throw; 59 | _; 60 | } 61 | 62 | modifier confirmed(uint transactionId, address owner) { 63 | if (!confirmations[transactionId][owner]) 64 | throw; 65 | _; 66 | } 67 | 68 | modifier notConfirmed(uint transactionId, address owner) { 69 | if (confirmations[transactionId][owner]) 70 | throw; 71 | _; 72 | } 73 | 74 | modifier notExecuted(uint transactionId) { 75 | if (transactions[transactionId].executed) 76 | throw; 77 | _; 78 | } 79 | 80 | modifier notNull(address _address) { 81 | if (_address == 0) 82 | throw; 83 | _; 84 | } 85 | 86 | modifier validRequirement(uint ownerCount, uint _required) { 87 | if ( ownerCount > MAX_OWNER_COUNT 88 | || _required > ownerCount 89 | || _required == 0 90 | || ownerCount == 0) 91 | throw; 92 | _; 93 | } 94 | 95 | /// @dev Fallback function allows to deposit ether. 96 | function() 97 | payable 98 | { 99 | if (msg.value > 0) 100 | Deposit(msg.sender, msg.value); 101 | } 102 | 103 | /* 104 | * Public functions 105 | */ 106 | /// @dev Contract constructor sets initial owners and required number of confirmations. 107 | /// @param _owners List of initial owners. 108 | /// @param _required Number of required confirmations. 109 | function MultiSigWallet(address[] _owners, uint _required) 110 | public 111 | validRequirement(_owners.length, _required) 112 | { 113 | for (uint i=0; i<_owners.length; i++) { 114 | if (isOwner[_owners[i]] || _owners[i] == 0) 115 | throw; 116 | isOwner[_owners[i]] = true; 117 | } 118 | owners = _owners; 119 | required = _required; 120 | } 121 | 122 | /// @dev Allows to add a new owner. Transaction has to be sent by wallet. 123 | /// @param owner Address of new owner. 124 | function addOwner(address owner) 125 | public 126 | onlyWallet 127 | ownerDoesNotExist(owner) 128 | notNull(owner) 129 | validRequirement(owners.length + 1, required) 130 | { 131 | isOwner[owner] = true; 132 | owners.push(owner); 133 | OwnerAddition(owner); 134 | } 135 | 136 | /// @dev Allows to remove an owner. Transaction has to be sent by wallet. 137 | /// @param owner Address of owner. 138 | function removeOwner(address owner) 139 | public 140 | onlyWallet 141 | ownerExists(owner) 142 | { 143 | isOwner[owner] = false; 144 | for (uint i=0; i owners.length) 151 | changeRequirement(owners.length); 152 | OwnerRemoval(owner); 153 | } 154 | 155 | /// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet. 156 | /// @param owner Address of owner to be replaced. 157 | /// @param owner Address of new owner. 158 | function replaceOwner(address owner, address newOwner) 159 | public 160 | onlyWallet 161 | ownerExists(owner) 162 | ownerDoesNotExist(newOwner) 163 | { 164 | for (uint i=0; i bool) public mintAgents; 23 | 24 | /** 25 | * Create new tokens and allocate them to an address.. 26 | * 27 | * Only callably by a crowdsale contract (mint agent). 28 | */ 29 | function mint(address receiver, uint amount) onlyMintAgent canMint public { 30 | 31 | if(amount == 0) { 32 | throw; 33 | } 34 | 35 | totalSupply = totalSupply.plus(amount); 36 | balances[receiver] = balances[receiver].plus(amount); 37 | Minted(receiver, amount); 38 | } 39 | 40 | /** 41 | * Owner can allow a crowdsale contract to mint new tokens. 42 | */ 43 | function setMintAgent(address addr, bool state) onlyOwner canMint public { 44 | mintAgents[addr] = state; 45 | } 46 | 47 | modifier onlyMintAgent() { 48 | // Only crowdsale contracts are allowed to mint new tokens 49 | if(!mintAgents[msg.sender]) { 50 | throw; 51 | } 52 | _; 53 | } 54 | 55 | /** Make sure we are not done yet. */ 56 | modifier canMint() { 57 | if(mintingFinished) throw; 58 | _; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/MultiVault.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | 4 | import "./Crowdsale.sol"; 5 | import "./SafeMathLib.sol"; 6 | import "./StandardToken.sol"; 7 | 8 | /** 9 | * Collect funds from presale investors, buy tokens for them in a single transaction and distribute out tokens. 10 | * 11 | * - Collect funds from pre-sale investors 12 | * - Send funds to the crowdsale when it opens 13 | * - Allow owner to set the crowdsale 14 | * - Have refund after X days as a safety hatch if the crowdsale doesn't materilize 15 | * - Allow unlimited investors 16 | * 17 | */ 18 | contract MultiVault is Ownable { 19 | 20 | using SafeMathLib for uint; 21 | 22 | /** How many investors we have now */ 23 | uint public investorCount; 24 | 25 | /** How many wei we have raised total. We use this as the distribution total amount. However because investors are added by hand this can be direct percentages too. */ 26 | uint public weiRaisedTotal; 27 | 28 | /** Who are our investors (iterable) */ 29 | address[] public investors; 30 | 31 | /** How much they have invested */ 32 | mapping(address => uint) public balances; 33 | 34 | /** How many tokens investors have claimed */ 35 | mapping(address => uint) public claimed; 36 | 37 | /** When our claim freeze is over (UNIT timestamp) */ 38 | uint public freezeEndsAt; 39 | 40 | /** Our ICO contract where we will move the funds */ 41 | Crowdsale public crowdsale; 42 | 43 | /** We can also define our own token, which will override the ICO one ***/ 44 | FractionalERC20 public token; 45 | 46 | /** How many tokens were deposited on the vautl */ 47 | uint public initialTokenBalance; 48 | 49 | /* Has owner set the initial balance */ 50 | bool public initialTokenBalanceFetched; 51 | 52 | /** What is our current state. */ 53 | enum State{Unknown, Holding, Distributing} 54 | 55 | /** Somebody loaded their investment money */ 56 | event Invested(address investor, uint value); 57 | 58 | /** We distributed tokens to an investor */ 59 | event Distributed(address investors, uint count); 60 | 61 | /** 62 | * Create presale contract where lock up period is given days 63 | */ 64 | function MultiVault(address _owner, uint _freezeEndsAt) { 65 | 66 | owner = _owner; 67 | 68 | // Give argument 69 | if(_freezeEndsAt == 0) { 70 | throw; 71 | } 72 | 73 | freezeEndsAt = _freezeEndsAt; 74 | } 75 | 76 | /** 77 | * Get the token we are distributing. 78 | */ 79 | function getToken() public constant returns(FractionalERC20) { 80 | if (address(token) > 0) 81 | return token; 82 | 83 | if(address(crowdsale) == 0) { 84 | throw; 85 | } 86 | 87 | return crowdsale.token(); 88 | } 89 | 90 | /** 91 | * Participate to a presale. 92 | */ 93 | function addInvestor(address investor, uint amount) public onlyOwner { 94 | 95 | // Cannot invest anymore through crowdsale when moving has begun 96 | if(getState() != State.Holding) throw; 97 | 98 | if(amount == 0) throw; // No empty buys 99 | 100 | bool existing = balances[investor] > 0; 101 | 102 | if(existing) { 103 | // Guarantee data load against race conditiosn 104 | // and fat fingers, so that we can load one investor only once 105 | throw; 106 | } 107 | 108 | balances[investor] = balances[investor].plus(amount); 109 | 110 | // This is a new investor 111 | if(!existing) { 112 | investors.push(investor); 113 | investorCount++; 114 | } 115 | 116 | weiRaisedTotal = weiRaisedTotal.plus(amount); 117 | 118 | Invested(investor, amount); 119 | } 120 | 121 | /** 122 | * How may tokens each investor gets. 123 | */ 124 | function getClaimAmount(address investor) public constant returns (uint) { 125 | 126 | if(!initialTokenBalanceFetched) { 127 | throw; 128 | } 129 | 130 | return initialTokenBalance.times(balances[investor]) / weiRaisedTotal; 131 | } 132 | 133 | /** 134 | * How many tokens remain unclaimed for an investor. 135 | */ 136 | function getClaimLeft(address investor) public constant returns (uint) { 137 | return getClaimAmount(investor).minus(claimed[investor]); 138 | } 139 | 140 | /** 141 | * Claim all remaining tokens for this investor. 142 | */ 143 | function claimAll() { 144 | claim(getClaimLeft(msg.sender)); 145 | } 146 | 147 | /** 148 | * Only owner is allowed to set the vault initial token balance. 149 | * 150 | * Because only owner can guarantee that the all tokens have been moved 151 | * to the vault and it can begin disribution. Otherwise somecone can 152 | * call this too early and lock the balance to zero or some other bad value. 153 | */ 154 | function fetchTokenBalance() onlyOwner { 155 | // Caching fetched token amount: 156 | if (!initialTokenBalanceFetched) { 157 | initialTokenBalance = getToken().balanceOf(address(this)); 158 | if(initialTokenBalance == 0) throw; // Somehow in invalid state 159 | initialTokenBalanceFetched = true; 160 | } else { 161 | throw; 162 | } 163 | } 164 | 165 | /** 166 | * Claim N bought tokens to the investor as the msg sender. 167 | * 168 | */ 169 | function claim(uint amount) { 170 | address investor = msg.sender; 171 | 172 | if(!initialTokenBalanceFetched) { 173 | // We need to have the balance before we start 174 | throw; 175 | } 176 | 177 | if(getState() != State.Distributing) { 178 | // We are not distributing yet 179 | throw; 180 | } 181 | 182 | if(getClaimLeft(investor) < amount) { 183 | // Woops we cannot get more than we have left 184 | throw; 185 | } 186 | 187 | claimed[investor] = claimed[investor].plus(amount); 188 | getToken().transfer(investor, amount); 189 | 190 | Distributed(investor, amount); 191 | } 192 | 193 | /** 194 | * Set the target crowdsale where we will move presale funds when the crowdsale opens. 195 | */ 196 | function setCrowdsale(Crowdsale _crowdsale) public onlyOwner { 197 | crowdsale = _crowdsale; 198 | } 199 | 200 | /** 201 | * Set the target token, which overrides the ICO token. 202 | */ 203 | function setToken(FractionalERC20 _token) public onlyOwner { 204 | token = _token; 205 | } 206 | 207 | /** 208 | * Resolve the contract umambigious state. 209 | */ 210 | function getState() public returns(State) { 211 | if(now > freezeEndsAt && initialTokenBalanceFetched) { 212 | return State.Distributing; 213 | } else { 214 | return State.Holding; 215 | } 216 | } 217 | 218 | /** Explicitly call function from your wallet. */ 219 | function() payable { 220 | throw; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /contracts/MysteriumCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.7; 2 | 3 | import "./Crowdsale.sol"; 4 | import "./MysteriumPricing.sol"; 5 | import "./MintableToken.sol"; 6 | 7 | 8 | contract MysteriumCrowdsale is Crowdsale { 9 | using SafeMathLib for uint; 10 | 11 | // Are we on the "end slope" (triggered after soft cap) 12 | bool public softCapTriggered; 13 | 14 | // The default minimum funding limit 7,000,000 CHF 15 | uint public minimumFundingCHF = 700000 * 10000; 16 | 17 | uint public hardCapCHF = 14000000 * 10000; 18 | 19 | function MysteriumCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end) 20 | Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, 0) { 21 | } 22 | 23 | /// @dev triggerSoftCap triggers the earlier closing time 24 | function triggerSoftCap() private { 25 | if(softCapTriggered) 26 | throw; 27 | 28 | uint softCap = MysteriumPricing(pricingStrategy).getSoftCapInWeis(); 29 | 30 | if(softCap > weiRaised) 31 | throw; 32 | 33 | // When contracts are updated from upstream, you should use: 34 | // setEndsAt (now + 24 hours); 35 | endsAt = now + (3*24*3600); 36 | EndsAtChanged(endsAt); 37 | 38 | softCapTriggered = true; 39 | } 40 | 41 | /** 42 | * Hook in to provide the soft cap time bomb. 43 | */ 44 | function onInvest() internal { 45 | if(!softCapTriggered) { 46 | uint softCap = MysteriumPricing(pricingStrategy).getSoftCapInWeis(); 47 | if(weiRaised > softCap) { 48 | triggerSoftCap(); 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Get minimum funding goal in wei. 55 | */ 56 | function getMinimumFundingGoal() public constant returns (uint goalInWei) { 57 | return MysteriumPricing(pricingStrategy).convertToWei(minimumFundingCHF); 58 | } 59 | 60 | /** 61 | * Allow reset the threshold. 62 | */ 63 | function setMinimumFundingLimit(uint chf) onlyOwner { 64 | minimumFundingCHF = chf; 65 | } 66 | 67 | /** 68 | * @return true if the crowdsale has raised enough money to be a succes 69 | */ 70 | function isMinimumGoalReached() public constant returns (bool reached) { 71 | return weiRaised >= getMinimumFundingGoal(); 72 | } 73 | 74 | function getHardCap() public constant returns (uint capInWei) { 75 | return MysteriumPricing(pricingStrategy).convertToWei(hardCapCHF); 76 | } 77 | 78 | /** 79 | * Reset hard cap. 80 | * 81 | * Give price in CHF * 10000 82 | */ 83 | function setHardCapCHF(uint _hardCapCHF) onlyOwner { 84 | hardCapCHF = _hardCapCHF; 85 | } 86 | 87 | /** 88 | * Called from invest() to confirm if the curret investment does not break our cap rule. 89 | */ 90 | function isBreakingCap(uint weiAmount, uint tokenAmount, uint weiRaisedTotal, uint tokensSoldTotal) constant returns (bool limitBroken) { 91 | return weiRaisedTotal > getHardCap(); 92 | } 93 | 94 | function isCrowdsaleFull() public constant returns (bool) { 95 | return weiRaised >= getHardCap(); 96 | } 97 | 98 | /** 99 | * @return true we have reached our soft cap 100 | */ 101 | function isSoftCapReached() public constant returns (bool reached) { 102 | return weiRaised >= MysteriumPricing(pricingStrategy).getSoftCapInWeis(); 103 | } 104 | 105 | 106 | /** 107 | * Dynamically create tokens and assign them to the investor. 108 | */ 109 | function assignTokens(address receiver, uint tokenAmount) private { 110 | MintableToken mintableToken = MintableToken(token); 111 | mintableToken.mint(receiver, tokenAmount); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /contracts/MysteriumPricing.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | import "./PricingStrategy.sol"; 4 | import "./SafeMathLib.sol"; 5 | import "./Crowdsale.sol"; 6 | import "zeppelin/contracts/ownership/Ownable.sol"; 7 | 8 | /** 9 | * Fixed crowdsale pricing - everybody gets the same price. 10 | */ 11 | contract MysteriumPricing is PricingStrategy, Ownable { 12 | 13 | using SafeMathLib for uint; 14 | 15 | // The conversion rate: how many weis is 1 CHF 16 | // https://www.coingecko.com/en/price_charts/ethereum/chf 17 | // 120.34587901 is 1203458 18 | uint public chfRate; 19 | 20 | uint public chfScale = 10000; 21 | 22 | /* How many weis one token costs */ 23 | uint public hardCapPrice = 12000; // 1.2 * 10000 Expressed as CFH base points 24 | 25 | uint public softCapPrice = 10000; // 1.0 * 10000 Expressed as CFH base points 26 | 27 | uint public softCapCHF = 6000000 * 10000; // Soft cap set in CHF 28 | 29 | //Address of the ICO contract: 30 | Crowdsale public crowdsale; 31 | 32 | function MysteriumPricing(uint initialChfRate) { 33 | chfRate = initialChfRate; 34 | } 35 | 36 | /// @dev Setting crowdsale for setConversionRate() 37 | /// @param _crowdsale The address of our ICO contract 38 | function setCrowdsale(Crowdsale _crowdsale) onlyOwner { 39 | 40 | if(!_crowdsale.isCrowdsale()) { 41 | throw; 42 | } 43 | 44 | crowdsale = _crowdsale; 45 | } 46 | 47 | /// @dev Here you can set the new CHF/ETH rate 48 | /// @param _chfRate The rate how many weis is one CHF 49 | function setConversionRate(uint _chfRate) onlyOwner { 50 | //Here check if ICO is active 51 | if(now > crowdsale.startsAt()) 52 | throw; 53 | 54 | chfRate = _chfRate; 55 | } 56 | 57 | /** 58 | * Allow to set soft cap. 59 | */ 60 | function setSoftCapCHF(uint _softCapCHF) onlyOwner { 61 | softCapCHF = _softCapCHF; 62 | } 63 | 64 | /** 65 | * Get CHF/ETH pair as an integer. 66 | * 67 | * Used in distribution calculations. 68 | */ 69 | function getEthChfPrice() public constant returns (uint) { 70 | return chfRate / chfScale; 71 | } 72 | 73 | /** 74 | * Currency conversion 75 | * 76 | * @param chf CHF price * 100000 77 | * @return wei price 78 | */ 79 | function convertToWei(uint chf) public constant returns(uint) { 80 | return chf.times(10**18) / chfRate; 81 | } 82 | 83 | /// @dev Function which tranforms CHF softcap to weis 84 | function getSoftCapInWeis() public returns (uint) { 85 | return convertToWei(softCapCHF); 86 | } 87 | 88 | /** 89 | * Calculate the current price for buy in amount. 90 | * 91 | * @param {uint amount} How many tokens we get 92 | */ 93 | function calculatePrice(uint value, uint weiRaised, uint tokensSold, address msgSender, uint decimals) public constant returns (uint) { 94 | 95 | uint multiplier = 10 ** decimals; 96 | if (weiRaised > getSoftCapInWeis()) { 97 | //Here SoftCap is not active yet 98 | return value.times(multiplier) / convertToWei(hardCapPrice); 99 | } else { 100 | return value.times(multiplier) / convertToWei(softCapPrice); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /contracts/MysteriumToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.7; 2 | 3 | import "./CrowdsaleToken.sol"; 4 | 5 | contract MysteriumToken is CrowdsaleToken { 6 | function MysteriumToken(string _name, string _symbol, uint _initialSupply, uint _decimals) 7 | CrowdsaleToken(_name, _symbol, _initialSupply, _decimals) { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/MysteriumTokenDistribution.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | import "./Crowdsale.sol"; 4 | import "./CrowdsaleToken.sol"; 5 | import "./SafeMathLib.sol"; 6 | import "./MysteriumPricing.sol"; 7 | import "zeppelin/contracts/ownership/Ownable.sol"; 8 | 9 | /** 10 | * At the end of the successful crowdsale allocate % bonus of tokens and other parties. 11 | * 12 | * Unlock tokens. 13 | * 14 | */ 15 | contract MysteriumTokenDistribution is FinalizeAgent, Ownable { 16 | 17 | using SafeMathLib for uint; 18 | 19 | CrowdsaleToken public token; 20 | Crowdsale public crowdsale; 21 | 22 | MysteriumPricing public mysteriumPricing; 23 | 24 | // Vaults: 25 | address futureRoundVault; 26 | address foundationWallet; 27 | address teamVault; 28 | address seedVault1; // 0 29 | address seedVault2; // 12 months 30 | 31 | // Expose the state of distribute for the examination 32 | uint public future_round_coins; 33 | uint public foundation_coins; 34 | uint public team_coins; 35 | uint public seed_coins_vault1; 36 | uint public seed_coins_vault2; 37 | 38 | function MysteriumTokenDistribution(CrowdsaleToken _token, Crowdsale _crowdsale, MysteriumPricing _mysteriumPricing) { 39 | token = _token; 40 | crowdsale = _crowdsale; 41 | 42 | // Interface check 43 | if(!crowdsale.isCrowdsale()) { 44 | throw; 45 | } 46 | 47 | mysteriumPricing = _mysteriumPricing; 48 | } 49 | 50 | /** 51 | * Post crowdsale distribution process. 52 | * 53 | * Exposed as public to make it testable. 54 | */ 55 | function distribute(uint amount_raised_chf, uint eth_chf_price) { 56 | 57 | // Only crowdsale contract or owner (manually) can trigger the distribution 58 | if(!(msg.sender == address(crowdsale) || msg.sender == owner)) { 59 | throw; 60 | } 61 | 62 | // Distribute: 63 | // seed coins 64 | // foundation coins 65 | // team coins 66 | // future_round_coins 67 | 68 | future_round_coins = 486500484333000; 69 | foundation_coins = 291900290600000; 70 | team_coins = 324333656222000; 71 | seed_coins_vault1 = 122400000000000; 72 | seed_coins_vault2 = 489600000000000; 73 | 74 | token.mint(futureRoundVault, future_round_coins); 75 | token.mint(foundationWallet, foundation_coins); 76 | token.mint(teamVault, team_coins); 77 | token.mint(seedVault1, seed_coins_vault1); 78 | token.mint(seedVault2, seed_coins_vault2); 79 | } 80 | 81 | /// @dev Here you can set all the Vaults 82 | function setVaults( 83 | address _futureRoundVault, 84 | address _foundationWallet, 85 | address _teamVault, 86 | address _seedVault1, 87 | address _seedVault2 88 | ) onlyOwner { 89 | futureRoundVault = _futureRoundVault; 90 | foundationWallet = _foundationWallet; 91 | teamVault = _teamVault; 92 | seedVault1 = _seedVault1; 93 | seedVault2 = _seedVault2; 94 | } 95 | 96 | /* Can we run finalize properly */ 97 | function isSane() public constant returns (bool) { 98 | // TODO: Check all vaults implement the correct vault interface 99 | return true; 100 | } 101 | 102 | function getDistributionFacts() public constant returns (uint chfRaised, uint chfRate) { 103 | uint _chfRate = mysteriumPricing.getEthChfPrice(); 104 | return(crowdsale.weiRaised().times(_chfRate) / (10**18), _chfRate); 105 | } 106 | 107 | /** Called once by crowdsale finalize() if the sale was success. */ 108 | function finalizeCrowdsale() public { 109 | 110 | if(msg.sender == address(crowdsale) || msg.sender == owner) { 111 | // The owner can distribute tokens for testing and in emergency 112 | // Crowdsale distributes tokens at the end of the crowdsale 113 | var (chfRaised, chfRate) = getDistributionFacts(); 114 | distribute(chfRaised, chfRate); 115 | } else { 116 | throw; 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /contracts/PricingStrategy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | /** 4 | * Interface for defining crowdsale pricing. 5 | */ 6 | contract PricingStrategy { 7 | 8 | /** Interface declaration. */ 9 | function isPricingStrategy() public constant returns (bool) { 10 | return true; 11 | } 12 | 13 | /** Self check if all references are correctly set. 14 | * 15 | * Checks that pricing strategy matches crowdsale parameters. 16 | */ 17 | function isSane(address crowdsale) public constant returns (bool) { 18 | return true; 19 | } 20 | 21 | /** 22 | * When somebody tries to buy tokens for X eth, calculate how many tokens they get. 23 | * 24 | * 25 | * @param value - What is the value of the transaction send in as wei 26 | * @param tokensSold - how much tokens have been sold this far 27 | * @param weiRaised - how much money has been raised this far 28 | * @param msgSender - who is the investor of this transaction 29 | * @param decimals - how many decimal units the token has 30 | * @return Amount of tokens the investor receives 31 | */ 32 | function calculatePrice(uint value, uint tokensSold, uint weiRaised, address msgSender, uint decimals) public constant returns (uint tokenAmount); 33 | } 34 | -------------------------------------------------------------------------------- /contracts/ReleasableToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "zeppelin/contracts/ownership/Ownable.sol"; 4 | import "zeppelin/contracts/token/ERC20.sol"; 5 | 6 | 7 | /** 8 | * Define interface for releasing the token transfer after a successful crowdsale. 9 | */ 10 | contract ReleasableToken is ERC20, Ownable { 11 | 12 | /* The finalizer contract that allows unlift the transfer limits on this token */ 13 | address public releaseAgent; 14 | 15 | /** A crowdsale contract can release us to the wild if ICO success. If false we are are in transfer lock up period.*/ 16 | bool public released = false; 17 | 18 | /** Map of agents that are allowed to transfer tokens regardless of the lock down period. These are crowdsale contracts and possible the team multisig itself. */ 19 | mapping (address => bool) public transferAgents; 20 | 21 | /** 22 | * Limit token transfer until the crowdsale is over. 23 | * 24 | */ 25 | modifier canTransfer(address _sender) { 26 | 27 | if(!released) { 28 | if(!transferAgents[_sender]) { 29 | throw; 30 | } 31 | } 32 | 33 | _; 34 | } 35 | 36 | /** 37 | * Set the contract that can call release and make the token transferable. 38 | * 39 | * Design choice. Allow reset the release agent to fix fat finger mistakes. 40 | */ 41 | function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public { 42 | 43 | // We don't do interface check here as we might want to a normal wallet address to act as a release agent 44 | releaseAgent = addr; 45 | } 46 | 47 | /** 48 | * Owner can allow a particular address (a crowdsale contract) to transfer tokens despite the lock up period. 49 | */ 50 | function setTransferAgent(address addr, bool state) onlyOwner inReleaseState(false) public { 51 | transferAgents[addr] = state; 52 | } 53 | 54 | /** 55 | * One way function to release the tokens to the wild. 56 | * 57 | * Can be called only from the release agent that is the final ICO contract. It is only called if the crowdsale has been success (first milestone reached). 58 | */ 59 | function releaseTokenTransfer() public onlyReleaseAgent { 60 | released = true; 61 | } 62 | 63 | /** The function can be called only before or after the tokens have been releasesd */ 64 | modifier inReleaseState(bool releaseState) { 65 | if(releaseState != released) { 66 | throw; 67 | } 68 | _; 69 | } 70 | 71 | /** The function can be called only by a whitelisted release agent. */ 72 | modifier onlyReleaseAgent() { 73 | if(msg.sender != releaseAgent) { 74 | throw; 75 | } 76 | _; 77 | } 78 | 79 | function transfer(address _to, uint _value) canTransfer(msg.sender) returns (bool success) { 80 | // Call StandardToken.transfer() 81 | return super.transfer(_to, _value); 82 | } 83 | 84 | function transferFrom(address _from, address _to, uint _value) canTransfer(_from) returns (bool success) { 85 | // Call StandardToken.transferForm() 86 | return super.transferFrom(_from, _to, _value); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /contracts/SafeMathLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | /** 4 | * Safe unsigned safe math. 5 | * 6 | * https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736#.750gwtwli 7 | * 8 | * Originally from https://raw.githubusercontent.com/AragonOne/zeppelin-solidity/master/contracts/SafeMathLib.sol 9 | * 10 | * Maintained here until merged to mainline zeppelin-solidity. 11 | * 12 | */ 13 | library SafeMathLib { 14 | 15 | function times(uint a, uint b) returns (uint) { 16 | uint c = a * b; 17 | assert(a == 0 || c / a == b); 18 | return c; 19 | } 20 | 21 | function minus(uint a, uint b) returns (uint) { 22 | assert(b <= a); 23 | return a - b; 24 | } 25 | 26 | function plus(uint a, uint b) returns (uint) { 27 | uint c = a + b; 28 | assert(c>=a && c>=b); 29 | return c; 30 | } 31 | 32 | function assert(bool assertion) private { 33 | if (!assertion) throw; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/StandardToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | 4 | import 'zeppelin/contracts/token/ERC20.sol'; 5 | import 'zeppelin/contracts/SafeMath.sol'; 6 | 7 | 8 | /** 9 | * Standard ERC20 token with Short Hand Attack and approve() race condition mitigation. 10 | * 11 | * Based on code by FirstBlood: 12 | * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 13 | */ 14 | contract StandardToken is ERC20, SafeMath { 15 | 16 | /* Token supply got increased and a new owner received these tokens */ 17 | event Minted(address receiver, uint amount); 18 | 19 | /* Actual balances of token holders */ 20 | mapping(address => uint) balances; 21 | 22 | /* approve() allowances */ 23 | mapping (address => mapping (address => uint)) allowed; 24 | 25 | /** 26 | * 27 | * Fix for the ERC20 short address attack 28 | * 29 | * http://vessenes.com/the-erc20-short-address-attack-explained/ 30 | */ 31 | modifier onlyPayloadSize(uint size) { 32 | if(msg.data.length != size + 4) { 33 | throw; 34 | } 35 | _; 36 | } 37 | 38 | function transfer(address _to, uint _value) onlyPayloadSize(2 * 32) returns (bool success) { 39 | balances[msg.sender] = safeSub(balances[msg.sender], _value); 40 | balances[_to] = safeAdd(balances[_to], _value); 41 | Transfer(msg.sender, _to, _value); 42 | return true; 43 | } 44 | 45 | function transferFrom(address _from, address _to, uint _value) returns (bool success) { 46 | uint _allowance = allowed[_from][msg.sender]; 47 | 48 | // Check is not needed because safeSub(_allowance, _value) will already throw if this condition is not met 49 | // if (_value > _allowance) throw; 50 | 51 | balances[_to] = safeAdd(balances[_to], _value); 52 | balances[_from] = safeSub(balances[_from], _value); 53 | allowed[_from][msg.sender] = safeSub(_allowance, _value); 54 | Transfer(_from, _to, _value); 55 | return true; 56 | } 57 | 58 | function balanceOf(address _owner) constant returns (uint balance) { 59 | return balances[_owner]; 60 | } 61 | 62 | function approve(address _spender, uint _value) returns (bool success) { 63 | 64 | // To change the approve amount you first have to reduce the addresses` 65 | // allowance to zero by calling `approve(_spender, 0)` if it is not 66 | // already 0 to mitigate the race condition described here: 67 | // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 68 | if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw; 69 | 70 | allowed[msg.sender][_spender] = _value; 71 | Approval(msg.sender, _spender, _value); 72 | return true; 73 | } 74 | 75 | function allowance(address _owner, address _spender) constant returns (uint remaining) { 76 | return allowed[_owner][_spender]; 77 | } 78 | 79 | /** 80 | * Atomic increment of approved spending 81 | * 82 | * Works around https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 83 | * 84 | */ 85 | function addApproval(address _spender, uint _addedValue) 86 | onlyPayloadSize(2 * 32) 87 | returns (bool success) { 88 | uint oldValue = allowed[msg.sender][_spender]; 89 | allowed[msg.sender][_spender] = safeAdd(oldValue, _addedValue); 90 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 91 | return true; 92 | } 93 | 94 | /** 95 | * Atomic decrement of approved spending. 96 | * 97 | * Works around https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 98 | */ 99 | function subApproval(address _spender, uint _subtractedValue) 100 | onlyPayloadSize(2 * 32) 101 | returns (bool success) { 102 | 103 | uint oldVal = allowed[msg.sender][_spender]; 104 | 105 | if (_subtractedValue > oldVal) { 106 | allowed[msg.sender][_spender] = 0; 107 | } else { 108 | allowed[msg.sender][_spender] = safeSub(oldVal, _subtractedValue); 109 | } 110 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 111 | return true; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /contracts/UpgradeAgent.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | /** 4 | * Upgrade agent interface inspired by Lunyr. 5 | * 6 | * Upgrade agent transfers tokens to a new contract. 7 | * Upgrade agent itself can be the token contract, or just a middle man contract doing the heavy lifting. 8 | */ 9 | contract UpgradeAgent { 10 | 11 | uint public originalSupply; 12 | 13 | /** Interface marker */ 14 | function isUpgradeAgent() public constant returns (bool) { 15 | return true; 16 | } 17 | 18 | function upgradeFrom(address _from, uint256 _value) public; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /contracts/UpgradeableToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "zeppelin/contracts/token/ERC20.sol"; 4 | import "./StandardToken.sol"; 5 | import "./UpgradeAgent.sol"; 6 | 7 | /** 8 | * A token upgrade mechanism where users can opt-in amount of tokens to the next smart contract revision. 9 | * 10 | * First envisioned by Golem and Lunyr projects. 11 | */ 12 | contract UpgradeableToken is StandardToken { 13 | 14 | /** Contract / person who can set the upgrade path. This can be the same as team multisig wallet, as what it is with its default value. */ 15 | address public upgradeMaster; 16 | 17 | /** The next contract where the tokens will be migrated. */ 18 | UpgradeAgent public upgradeAgent; 19 | 20 | /** How many tokens we have upgraded by now. */ 21 | uint256 public totalUpgraded; 22 | 23 | /** 24 | * Upgrade states. 25 | * 26 | * - NotAllowed: The child contract has not reached a condition where the upgrade can bgun 27 | * - WaitingForAgent: Token allows upgrade, but we don't have a new agent yet 28 | * - ReadyToUpgrade: The agent is set, but not a single token has been upgraded yet 29 | * - Upgrading: Upgrade agent is set and the balance holders can upgrade their tokens 30 | * 31 | */ 32 | enum UpgradeState {Unknown, NotAllowed, WaitingForAgent, ReadyToUpgrade, Upgrading} 33 | 34 | /** 35 | * Somebody has upgraded some of his tokens. 36 | */ 37 | event Upgrade(address indexed _from, address indexed _to, uint256 _value); 38 | 39 | /** 40 | * New upgrade agent available. 41 | */ 42 | event UpgradeAgentSet(address agent); 43 | 44 | /** 45 | * Do not allow construction without upgrade master set. 46 | */ 47 | function UpgradeableToken(address _upgradeMaster) { 48 | upgradeMaster = _upgradeMaster; 49 | } 50 | 51 | /** 52 | * Allow the token holder to upgrade some of their tokens to a new contract. 53 | */ 54 | function upgrade(uint256 value) public { 55 | 56 | UpgradeState state = getUpgradeState(); 57 | if(!(state == UpgradeState.ReadyToUpgrade || state == UpgradeState.Upgrading)) { 58 | // Called in a bad state 59 | throw; 60 | } 61 | 62 | // Validate input value. 63 | if (value == 0) throw; 64 | 65 | balances[msg.sender] = safeSub(balances[msg.sender], value); 66 | 67 | // Take tokens out from circulation 68 | totalSupply = safeSub(totalSupply, value); 69 | totalUpgraded = safeAdd(totalUpgraded, value); 70 | 71 | // Upgrade agent reissues the tokens 72 | upgradeAgent.upgradeFrom(msg.sender, value); 73 | Upgrade(msg.sender, upgradeAgent, value); 74 | } 75 | 76 | /** 77 | * Set an upgrade agent that handles 78 | */ 79 | function setUpgradeAgent(address agent) external { 80 | 81 | if(!canUpgrade()) { 82 | // The token is not yet in a state that we could think upgrading 83 | throw; 84 | } 85 | 86 | if (agent == 0x0) throw; 87 | // Only a master can designate the next agent 88 | if (msg.sender != upgradeMaster) throw; 89 | // Upgrade has already begun for an agent 90 | if (getUpgradeState() == UpgradeState.Upgrading) throw; 91 | 92 | upgradeAgent = UpgradeAgent(agent); 93 | 94 | // Bad interface 95 | if(!upgradeAgent.isUpgradeAgent()) throw; 96 | // Make sure that token supplies match in source and target 97 | if (upgradeAgent.originalSupply() != totalSupply) throw; 98 | 99 | UpgradeAgentSet(upgradeAgent); 100 | } 101 | 102 | /** 103 | * Get the state of the token upgrade. 104 | */ 105 | function getUpgradeState() public constant returns(UpgradeState) { 106 | if(!canUpgrade()) return UpgradeState.NotAllowed; 107 | else if(address(upgradeAgent) == 0x00) return UpgradeState.WaitingForAgent; 108 | else if(totalUpgraded == 0) return UpgradeState.ReadyToUpgrade; 109 | else return UpgradeState.Upgrading; 110 | } 111 | 112 | /** 113 | * Change the upgrade master. 114 | * 115 | * This allows us to set a new owner for the upgrade mechanism. 116 | */ 117 | function setUpgradeMaster(address master) public { 118 | if (master == 0x0) throw; 119 | if (msg.sender != upgradeMaster) throw; 120 | upgradeMaster = master; 121 | } 122 | 123 | /** 124 | * Child contract can enable to provide the condition when the upgrade can begun. 125 | */ 126 | function canUpgrade() public constant returns(bool) { 127 | return true; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /contracts/test/SimpleReleaseAgent.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "../ReleasableToken.sol"; 4 | 5 | 6 | /** 7 | * To test transfer lock up release. 8 | */ 9 | contract SimpleReleaseAgent { 10 | 11 | ReleasableToken token; 12 | 13 | function SimpleReleaseAgent(ReleasableToken _token) { 14 | token = _token; 15 | } 16 | 17 | function release() { 18 | token.releaseTokenTransfer(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/test/TestMigrationTarget.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "../SafeMathLib.sol"; 4 | import "../UpgradeableToken.sol"; 5 | import "../MintableToken.sol"; 6 | 7 | /** 8 | * A sample token that is used as a migration testing target. 9 | * 10 | * This is not an actual token, but just a stub used in testing. 11 | */ 12 | contract TestMigrationTarget is StandardToken, UpgradeAgent { 13 | 14 | using SafeMathLib for uint; 15 | 16 | UpgradeableToken public oldToken; 17 | 18 | uint public originalSupply; 19 | 20 | function TestMigrationTarget(UpgradeableToken _oldToken) { 21 | 22 | oldToken = _oldToken; 23 | 24 | // Let's not set bad old token 25 | if(address(oldToken) == 0) { 26 | throw; 27 | } 28 | 29 | // Let's make sure we have something to migrate 30 | originalSupply = _oldToken.totalSupply(); 31 | if(originalSupply == 0) { 32 | throw; 33 | } 34 | } 35 | 36 | function upgradeFrom(address _from, uint256 _value) public { 37 | if (msg.sender != address(oldToken)) throw; // only upgrade from oldToken 38 | 39 | // Mint new tokens to the migrator 40 | totalSupply = totalSupply.plus(_value); 41 | balances[_from] = balances[_from].plus(_value); 42 | Transfer(0, _from, _value); 43 | } 44 | 45 | function() public payable { 46 | throw; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /crowdsales/README.txt: -------------------------------------------------------------------------------- 1 | Crowdsale definitions go here. 2 | -------------------------------------------------------------------------------- /crowdsales/import-investors.py: -------------------------------------------------------------------------------- 1 | from ico.utils import check_succesful_tx 2 | import populus 3 | import time 4 | from populus.utils.cli import request_account_unlock 5 | from populus.utils.accounts import is_account_locked 6 | from eth_utils import to_wei 7 | from web3 import Web3 8 | from web3.contract import Contract 9 | 10 | import uuid 11 | 12 | p = populus.Project() 13 | account = "0x" # Our controller account on Kovan 14 | 15 | def import_investor_data(contract: Contract, deploy_address: str, fname: str): 16 | """Load investor data to a MultiVault contract. 17 | 18 | Mysterium specific data loader. 19 | 20 | :return: List of unconfirmed transaction ids 21 | """ 22 | 23 | assert fname.endswith(".csv") 24 | 25 | txs = [] 26 | with open(fname, "rt") as inp: 27 | for line in inp: 28 | address, amount = line.split(",") 29 | address = address.strip() 30 | amount = amount.strip() 31 | assert address.startswith("0x") 32 | amount = int(float(amount) * 1000000000) # Use this precision. CHANGED 33 | if contract.call().balances(address) == 0: 34 | contract.transact({"from": deploy_address}).addInvestor(address, amount) 35 | #time.sleep(1) 36 | 37 | 38 | with p.get_chain("mainnet") as chain: 39 | web3 = chain.web3 40 | Contract = getattr(chain.contract_factories, "MultiVault") 41 | seed_participant_vault1 = Contract(address="0x35AfB92d3F2bE0D206E808355ca7bfAc9d820735") 42 | seed_participant_vault2 = Contract(address="0x1962A6183412360Ca64eD34c111efDabF08b909B") 43 | founders_vault = Contract(address="0x33222541eaE599124dF510D02f6e70DAdA1a9331") 44 | 45 | import_investor_data(seed_participant_vault1, "0x6efD5665ab4B345A7eBE63c679b651f375DDdB7E", "seed_investor_data.csv") 46 | import_investor_data(seed_participant_vault2, "0x6efD5665ab4B345A7eBE63c679b651f375DDdB7E", "seed_investor_data.csv") 47 | import_investor_data(founders_vault, "0x6efD5665ab4B345A7eBE63c679b651f375DDdB7E", "founders_data.csv") 48 | -------------------------------------------------------------------------------- /crowdsales/mysterium-kovan.yml: -------------------------------------------------------------------------------- 1 | kovan: 2 | 3 | chain: kovan 4 | 5 | verify_on_etherscan: yes 6 | 7 | # Firefox 53b has an issue that prevents correct Selenium automated 8 | # interaction with Etherscan form. 9 | # http://stackoverflow.com/q/43175013/315168 10 | browser_driver: chrome 11 | 12 | # 13 | # Contracts section defines different smart contracts that 14 | # are required to run the ICO. 15 | # 16 | contracts: 17 | 18 | # 4/5 vault for the sale funds 19 | team_multisig_funds: 20 | contract_name: MultiSigWallet 21 | contract_file: GnosisWallet.sol 22 | address: "0x7e6614722614e434c4df9901bab31e466ba12fa4" 23 | 24 | # Foundation wallet for tokens 25 | team_multisig_foundation: 26 | contract_name: MultiSigWallet 27 | contract_file: GnosisWallet.sol 28 | address: "0xe18ce35060f39ed5c68b92629230aadcd79ea6e3" 29 | 30 | # 31 | # Token contract 32 | # 33 | # This contract represents ERC-20 token. 34 | # It has transfer lock up functionality to prevent the token to be transferable 35 | # until the ICO is over. Furthermore it has dynamic minting supply, 36 | # so that the crowdsale contract will create new tokens as investment arrives in. 37 | # This way, there is no need for a separate burn stage at the end of the crowdsale, 38 | # as we never create the exceeding supply. 39 | # 40 | # 41 | token: 42 | contract_name: CrowdsaleToken 43 | contract_file: CrowdsaleToken.sol 44 | arguments: 45 | _name: Muiresym 46 | _symbol: TSYM 47 | _initialSupply: 0 48 | _decimals: 8 49 | # 50 | # Pricing strategy 51 | # 52 | pricing_strategy: 53 | contract_name: MysteriumPricing 54 | contract_file: MysteriumPricing.sol 55 | arguments: 56 | initialChfRate: "{{ 188 * 10000 }}" 57 | 58 | # 59 | # Intermediate vault to hold team funds for 4 weeks 60 | # 61 | intermediate_vault: 62 | contract_name: IntermediateVault 63 | contract_file: IntermediateVault.sol 64 | arguments: 65 | _teamMultisig: "{{ contracts.team_multisig_funds.address }}" 66 | _unlockedAt: "{{ 1 }}" # TODO 67 | 68 | seed_participant_vault: 69 | contract_name: MultiVault 70 | contract_file: MultiVault.sol 71 | arguments: 72 | _owner: "{{ deploy_address }}" 73 | _freezeEndsAt: "{{ 1 }}" # TODO 74 | 75 | seed_participant_vault_2: 76 | contract_name: MultiVault 77 | contract_file: MultiVault.sol 78 | arguments: 79 | _owner: "{{ deploy_address }}" 80 | _freezeEndsAt: "{{ time() + 12*30*24*3600 }}" # 12 months 81 | 82 | founders_vault: 83 | contract_name: MultiVault 84 | contract_file: MultiVault.sol 85 | arguments: 86 | _owner: "{{ deploy_address }}" 87 | _freezeEndsAt: "{{ 1 }}" # TODO 88 | 89 | future_funding_vault: 90 | contract_name: MultiVault 91 | contract_file: MultiVault.sol 92 | arguments: 93 | _owner: "{{ deploy_address }}" 94 | _freezeEndsAt: "{{ 1 }}" # TODO 95 | 96 | # Crowdsale 97 | # 98 | # This is the actual crowdsale contract. 99 | # It will accept investments during the crowdsale time period. 100 | # For each investments, it asks the pricing contract for the 101 | # current price for this particular investor. 102 | # 103 | # 104 | # 105 | crowdsale: 106 | contract_name: MysteriumCrowdsale 107 | contract_file: MysteriumCrowdsale.sol 108 | arguments: 109 | _token: "{{contracts.token.address}}" 110 | _pricingStrategy: "{{contracts.pricing_strategy.address}}" 111 | _multisigWallet: "{{contracts.intermediate_vault.address}}" 112 | _start: "{{ timestamp(datetime(2000, 5, 30, 13, 00)) }}" # TODO: non-final testing time 113 | _end: "{{ timestamp(datetime(2017, 6, 30, 13, 00)) }}" 114 | # 115 | # At the end of the crowdsale 116 | # sold tokens: 30% 117 | # team tokens: 70% 118 | # 119 | token_distribution: 120 | contract_name: MysteriumTokenDistribution 121 | contract_file: MysteriumTokenDistribution.sol 122 | arguments: 123 | _token: "{{contracts.token.address}}" 124 | _crowdsale: "{{contracts.crowdsale.address}}" 125 | _mysteriumPricing: "{{contracts.pricing_strategy.address}}" 126 | 127 | # Post-deployment actions connect contracts together. 128 | post_actions: | 129 | 130 | # Enable new token minting by a crowdsale contract 131 | confirm_tx(token.transact({"from": deploy_address}).setMintAgent(crowdsale.address, True)) 132 | 133 | # Enable token mintint at the end of the crowdsale to create bonus pool, founder pool and such 134 | confirm_tx(token.transact({"from": deploy_address}).setMintAgent(token_distribution.address, True)) 135 | confirm_tx(crowdsale.transact({"from": deploy_address}).setFinalizeAgent(token_distribution.address)) 136 | 137 | # Manual release token transfer at the end of the crowdsale 138 | confirm_tx(token.transact({"from": deploy_address}).setReleaseAgent(deploy_address)) 139 | 140 | # Set token upgrade master to team multisig to give the new token path 141 | confirm_tx(token.transact({"from": deploy_address}).setUpgradeMaster(team_multisig_funds.address)) 142 | 143 | # Allow pricing strategy to read crowdsale for the CHF rate chec 144 | confirm_tx(pricing_strategy.transact({"from": deploy_address}).setCrowdsale(crowdsale.address)) 145 | 146 | # Allow test buys from these accounts before crowdsale begins 147 | confirm_multiple_txs( \ 148 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist(deploy_address, True), \ 149 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist("0x1c5e4db864861D9b6203bd86Af0C0B5ffcD6115d", True), \ 150 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist("0x73cd5cb24682239cF38732B3740a193cf6CE83ac", True), \ 151 | ) 152 | 153 | # Set token distribution model 154 | confirm_tx( \ 155 | token_distribution.transact({"from": deploy_address}).setVaults( \ 156 | _futureRoundVault=future_funding_vault.address, \ 157 | _foundationWallet=team_multisig_foundation.address, \ 158 | _teamVault=founders_vault.address, \ 159 | _seedVault1=seed_participant_vault.address, \ 160 | _seedVault2=seed_participant_vault_2.address, \ 161 | ) \ 162 | ) 163 | 164 | # 165 | # Set vault crowdsale data 166 | # 167 | confirm_multiple_txs( \ 168 | seed_participant_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 169 | seed_participant_vault_2.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 170 | future_funding_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 171 | founders_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 172 | ) 173 | 174 | # 175 | # Load vault participant data 176 | # 177 | 178 | # Founders vault goes 100% to team multisig 179 | confirm_multiple_txs( \ 180 | founders_vault.transact({"from": deploy_address}).addInvestor(team_multisig_funds.address, 100), \ 181 | future_funding_vault.transact({"from": deploy_address}).addInvestor(team_multisig_foundation.address, 100), \ 182 | ) 183 | 184 | # Do not load during the deployment. Takes 33 txs that may risk the deployment. Load separately. 185 | # Seed vault 1 186 | # confirm_multiple_txs(*load_investor_data(seed_participant_vault, deploy_address, "fake_seed_investor_data.csv")) 187 | 188 | # Seed vault 2 189 | # TODO - using a test value as one address 190 | # confirm_multiple_txs(*load_investor_data(seed_participant_vault_2, deploy_address, "fake_seed_investor_data.csv")) 191 | 192 | # Sanity check 193 | verify_actions: | 194 | assert token.call().owner().lower() == deploy_address.lower() 195 | assert crowdsale.call().owner().lower() == deploy_address.lower() 196 | assert crowdsale.call().multisigWallet().lower() == intermediate_vault.address.lower() 197 | assert token_distribution.call().isSane() 198 | assert crowdsale.call().getState() in (CrowdsaleState.PreFunding, CrowdsaleState.Funding) 199 | 200 | # As the last action, after successful deployment, set the right token symbol and name so that it shows in a blockchain explorer 201 | confirm_tx(token.transact({"from": deploy_address}).setTokenInformation("Xterium", "XXX")) 202 | 203 | 204 | -------------------------------------------------------------------------------- /crowdsales/mysterium-mainnet.yml: -------------------------------------------------------------------------------- 1 | mainnet: 2 | 3 | chain: mainnet 4 | 5 | verify_on_etherscan: yes 6 | 7 | # Firefox 53b has an issue that prevents correct Selenium automated 8 | # interaction with Etherscan form. 9 | # http://stackoverflow.com/q/43175013/315168 10 | browser_driver: chrome 11 | 12 | # 13 | # Contracts section defines different smart contracts that 14 | # are required to run the ICO. 15 | # 16 | contracts: 17 | 18 | # 4/5 vault for the sale funds 19 | team_multisig_funds: 20 | contract_name: MultiSigWallet 21 | contract_file: GnosisWallet.sol 22 | address: "0x7e6614722614e434c4df9901bab31e466ba12fa4" 23 | 24 | # Foundation wallet for tokens 25 | team_multisig_foundation: 26 | contract_name: MultiSigWallet 27 | contract_file: GnosisWallet.sol 28 | address: "0xE18cE35060f39eD5c68B92629230aadcD79eA6E3" 29 | 30 | # 31 | # Token contract 32 | # 33 | # This contract represents ERC-20 token. 34 | # It has transfer lock up functionality to prevent the token to be transferable 35 | # until the ICO is over. Furthermore it has dynamic minting supply, 36 | # so that the crowdsale contract will create new tokens as investment arrives in. 37 | # This way, there is no need for a separate burn stage at the end of the crowdsale, 38 | # as we never create the exceeding supply. 39 | # 40 | # 41 | token: 42 | contract_name: CrowdsaleToken 43 | contract_file: CrowdsaleToken.sol 44 | arguments: 45 | _name: Muiresym 46 | _symbol: TSYM 47 | _initialSupply: 0 48 | _decimals: 8 49 | # 50 | # Pricing strategy 51 | # 52 | pricing_strategy: 53 | contract_name: MysteriumPricing 54 | contract_file: MysteriumPricing.sol 55 | arguments: 56 | initialChfRate: "{{ 184 * 10000 }}" 57 | 58 | # 59 | # Intermediate vault to hold team funds for 2 weeks, 60 | # then forwrads to funds wallet 61 | # 62 | intermediate_vault: 63 | contract_name: IntermediateVault 64 | contract_file: IntermediateVault.sol 65 | arguments: 66 | _teamMultisig: "{{ contracts.team_multisig_funds.address }}" 67 | _unlockedAt: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) + 14*24*3600 }}" 68 | 69 | # 70 | # Seed vault 1 locked for 2 days. 71 | # 2 days is nomimal time. 72 | # (tokens claimable after owner releases the token) 73 | # 74 | seed_participant_vault: 75 | contract_name: MultiVault 76 | contract_file: MultiVault.sol 77 | arguments: 78 | _owner: "{{ deploy_address }}" 79 | _freezeEndsAt: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) + 2*24*3600 }}" 80 | 81 | # 82 | # Seed vault 2 locked for 12 months 83 | # 84 | seed_participant_vault_2: 85 | contract_name: MultiVault 86 | contract_file: MultiVault.sol 87 | arguments: 88 | _owner: "{{ deploy_address }}" 89 | _freezeEndsAt: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) + 12*30*24*3600 }}" # 12 months 90 | 91 | # 92 | # Founder vault holds founder tokens for 12 months. 93 | # It 94 | # 95 | founders_vault: 96 | contract_name: MultiVault 97 | contract_file: MultiVault.sol 98 | arguments: 99 | _owner: "{{ deploy_address }}" 100 | _freezeEndsAt: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) + 12*30*24*3600 }}" 101 | 102 | # 103 | # Future funding vault 100% owner by foundation wallet, 104 | # as set later 105 | # 106 | future_funding_vault: 107 | contract_name: MultiVault 108 | contract_file: MultiVault.sol 109 | arguments: 110 | _owner: "{{ deploy_address }}" 111 | _freezeEndsAt: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) + 12*30*24*3600 }}" 112 | 113 | # Crowdsale 114 | # 115 | # This is the actual crowdsale contract. 116 | # It will accept investments during the crowdsale time period. 117 | # For each investments, it asks the pricing contract for the 118 | # current price for this particular investor. 119 | # 120 | # 121 | # 122 | crowdsale: 123 | contract_name: MysteriumCrowdsale 124 | contract_file: MysteriumCrowdsale.sol 125 | arguments: 126 | _token: "{{contracts.token.address}}" 127 | _pricingStrategy: "{{contracts.pricing_strategy.address}}" 128 | _multisigWallet: "{{contracts.intermediate_vault.address}}" # We lock up funds for 2 weeks 129 | _start: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) }}" 130 | _end: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) + 14*24*3600 }}" 131 | # 132 | # At the end of the crowdsale 133 | # sold tokens: 30% 134 | # team tokens: 70% 135 | # 136 | token_distribution: 137 | contract_name: MysteriumTokenDistribution 138 | contract_file: MysteriumTokenDistribution.sol 139 | arguments: 140 | _token: "{{contracts.token.address}}" 141 | _crowdsale: "{{contracts.crowdsale.address}}" 142 | _mysteriumPricing: "{{contracts.pricing_strategy.address}}" 143 | 144 | # Post-deployment actions connect contracts together. 145 | post_actions: | 146 | 147 | # Enable new token minting by a crowdsale contract 148 | confirm_tx(token.transact({"from": deploy_address}).setMintAgent(crowdsale.address, True)) 149 | 150 | # Enable token mintint at the end of the crowdsale to create bonus pool, founder pool and such 151 | confirm_tx(token.transact({"from": deploy_address}).setMintAgent(token_distribution.address, True)) 152 | confirm_tx(crowdsale.transact({"from": deploy_address}).setFinalizeAgent(token_distribution.address)) 153 | 154 | # Manual release token transfer at the end of the crowdsale 155 | confirm_tx(token.transact({"from": deploy_address}).setReleaseAgent(deploy_address)) 156 | 157 | # Set token upgrade master to team multisig to give the new token path 158 | confirm_tx(token.transact({"from": deploy_address}).setUpgradeMaster(team_multisig_funds.address)) 159 | 160 | # Allow pricing strategy to read crowdsale for the CHF rate chec 161 | confirm_tx(pricing_strategy.transact({"from": deploy_address}).setCrowdsale(crowdsale.address)) 162 | 163 | # Allow test buys from these accounts before token sale begins 164 | confirm_multiple_txs( \ 165 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist(deploy_address, True), \ 166 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist("0x1c5e4db864861D9b6203bd86Af0C0B5ffcD6115d", True), \ 167 | ) 168 | 169 | # Set token distribution model 170 | confirm_tx( \ 171 | token_distribution.transact({"from": deploy_address}).setVaults( \ 172 | _futureRoundVault=future_funding_vault.address, \ 173 | _foundationWallet=team_multisig_foundation.address, \ 174 | _teamVault=founders_vault.address, \ 175 | _seedVault1=seed_participant_vault.address, \ 176 | _seedVault2=seed_participant_vault_2.address, \ 177 | ) \ 178 | ) 179 | 180 | # 181 | # Set vault crowdsale data 182 | # 183 | confirm_multiple_txs( \ 184 | seed_participant_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 185 | seed_participant_vault_2.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 186 | future_funding_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 187 | founders_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 188 | ) 189 | 190 | # 191 | # Load vault participant data 192 | # 193 | 194 | # Future funding vault goes 100% to foundation address 195 | confirm_multiple_txs( \ 196 | future_funding_vault.transact({"from": deploy_address}).addInvestor(team_multisig_foundation.address, 100), \ 197 | ) 198 | 199 | # 200 | # Seed vault data is loaded after the deployment is done 201 | # 202 | 203 | # Do not load during the deployment. Takes 33 txs that may risk the deployment. Load separately. 204 | # Seed vault 1 205 | # confirm_multiple_txs(*load_investor_data(seed_participant_vault, deploy_address, "fake_seed_investor_data.csv")) 206 | 207 | # Seed vault 2 208 | # TODO - using a test value as one address 209 | # confirm_multiple_txs(*load_investor_data(seed_participant_vault_2, deploy_address, "fake_seed_investor_data.csv")) 210 | 211 | # 212 | # Founder vault 213 | # TODO 214 | # 215 | 216 | # Sanity check 217 | verify_actions: | 218 | assert token.call().owner().lower() == deploy_address.lower() 219 | assert crowdsale.call().owner().lower() == deploy_address.lower() 220 | assert crowdsale.call().multisigWallet().lower() == intermediate_vault.address.lower() 221 | assert token_distribution.call().isSane() 222 | assert crowdsale.call().getState() in (CrowdsaleState.PreFunding, CrowdsaleState.Funding) 223 | 224 | # Do a test buy 225 | confirm_tx(crowdsale.transact({"from": deploy_address, "value": to_wei("0.01", "ether")}).buy()) 226 | 227 | # As the last action, after successful deployment, set the right token symbol and name so that it shows in a blockchain explorer 228 | confirm_tx(token.transact({"from": deploy_address}).setTokenInformation("Mysterium", "MYST")) 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /crowdsales/mysterium-new-finalizeagent.deployment-report.yml: -------------------------------------------------------------------------------- 1 | 2 | chain: mainnet 3 | 4 | verify_on_etherscan: yes 5 | 6 | # Firefox 53b has an issue that prevents correct Selenium automated 7 | # interaction with Etherscan form. 8 | # http://stackoverflow.com/q/43175013/315168 9 | browser_driver: chrome 10 | 11 | # 12 | # Contracts section defines different smart contracts that 13 | # are required to run the ICO. 14 | # 15 | contracts: 16 | token_distribution: !!omap 17 | - contract_name: MysteriumTokenDistribution 18 | - contract_file: MysteriumTokenDistribution.sol 19 | - arguments: !!omap 20 | - _token: '0xa645264C5603E96c3b0B078cdab68733794B0A71' 21 | - _crowdsale: '0xE7E01BABb53c98f8d84d05dFff64Ebb63ed37bF5' 22 | - _mysteriumPricing: '0xD2B34DE9882923dF7a97fbE37F15Ba7c2E3DEEFD' 23 | - address: '0x25cdc41bf2c88e0b6ece3b922eb74c7169cd3df4' 24 | - constructor_args: '0x000000000000000000000000a645264c5603e96c3b0b078cdab68733794b0a71000000000000000000000000e7e01babb53c98f8d84d05dfff64ebb63ed37bf5000000000000000000000000d2b34de9882923df7a97fbe37f15ba7c2e3deefd' 25 | - libraries: 26 | SafeMathLib: '0x790d13e76b2c727086018842af947408e371868f' 27 | - etherscan_link: https://etherscan.io/address/0x25cdc41bf2c88e0b6ece3b922eb74c7169cd3df4 28 | deploy_address: '0x6efD5665ab4B345A7eBE63c679b651f375DDdB7E' 29 | -------------------------------------------------------------------------------- /crowdsales/mysterium-testrpc.yml: -------------------------------------------------------------------------------- 1 | kovan: 2 | 3 | chain: kovan 4 | 5 | verify_on_etherscan: no 6 | 7 | # Firefox 53b has an issue that prevents correct Selenium automated 8 | # interaction with Etherscan form. 9 | # http://stackoverflow.com/q/43175013/315168 10 | browser_driver: chrome 11 | 12 | # 13 | # Contracts section defines different smart contracts that 14 | # are required to run the ICO. 15 | # 16 | contracts: 17 | 18 | # 4/5 vault for the sale funds 19 | team_multisig_funds: 20 | contract_name: MultiSigWallet 21 | contract_file: GnosisWallet.sol 22 | address: "0x7e6614722614e434c4df9901bab31e466ba12fa4" 23 | 24 | # Foundation wallet for tokens 25 | team_multisig_foundation: 26 | contract_name: MultiSigWallet 27 | contract_file: GnosisWallet.sol 28 | address: "0xe18ce35060f39ed5c68b92629230aadcd79ea6e3" 29 | 30 | # 31 | # Token contract 32 | # 33 | # This contract represents ERC-20 token. 34 | # It has transfer lock up functionality to prevent the token to be transferable 35 | # until the ICO is over. Furthermore it has dynamic minting supply, 36 | # so that the crowdsale contract will create new tokens as investment arrives in. 37 | # This way, there is no need for a separate burn stage at the end of the crowdsale, 38 | # as we never create the exceeding supply. 39 | # 40 | # 41 | token: 42 | contract_name: CrowdsaleToken 43 | contract_file: CrowdsaleToken.sol 44 | arguments: 45 | _name: Muiresym 46 | _symbol: TSYM 47 | _initialSupply: 0 48 | _decimals: 8 49 | # 50 | # Pricing strategy 51 | # 52 | pricing_strategy: 53 | contract_name: MysteriumPricing 54 | contract_file: MysteriumPricing.sol 55 | arguments: 56 | initialChfRate: "{{ 188 * 10000 }}" 57 | 58 | # 59 | # Intermediate vault to hold team funds for 4 weeks 60 | # 61 | intermediate_vault: 62 | contract_name: IntermediateVault 63 | contract_file: IntermediateVault.sol 64 | arguments: 65 | _teamMultisig: "{{ contracts.team_multisig_funds.address }}" 66 | _unlockedAt: "{{ 1 }}" # TODO 67 | 68 | seed_participant_vault: 69 | contract_name: MultiVault 70 | contract_file: MultiVault.sol 71 | arguments: 72 | _owner: "{{ deploy_address }}" 73 | _freezeEndsAt: "{{ 1 }}" # TODO 74 | 75 | seed_participant_vault_2: 76 | contract_name: MultiVault 77 | contract_file: MultiVault.sol 78 | arguments: 79 | _owner: "{{ deploy_address }}" 80 | _freezeEndsAt: "{{ time() + 12*30*24*3600 }}" # 12 months 81 | 82 | founders_vault: 83 | contract_name: MultiVault 84 | contract_file: MultiVault.sol 85 | arguments: 86 | _owner: "{{ deploy_address }}" 87 | _freezeEndsAt: "{{ 1 }}" # TODO 88 | 89 | future_funding_vault: 90 | contract_name: MultiVault 91 | contract_file: MultiVault.sol 92 | arguments: 93 | _owner: "{{ deploy_address }}" 94 | _freezeEndsAt: "{{ 1 }}" # TODO 95 | 96 | # Crowdsale 97 | # 98 | # This is the actual crowdsale contract. 99 | # It will accept investments during the crowdsale time period. 100 | # For each investments, it asks the pricing contract for the 101 | # current price for this particular investor. 102 | # 103 | # 104 | # 105 | crowdsale: 106 | contract_name: MysteriumCrowdsale 107 | contract_file: MysteriumCrowdsale.sol 108 | arguments: 109 | _token: "{{contracts.token.address}}" 110 | _pricingStrategy: "{{contracts.pricing_strategy.address}}" 111 | _multisigWallet: "{{contracts.intermediate_vault.address}}" 112 | _start: "{{ timestamp(datetime(2017, 5, 30, 13, 00)) }}" # TODO: non-final testing time 113 | _end: "{{ timestamp(datetime(2017, 6, 30, 13, 00)) }}" 114 | # 115 | # At the end of the crowdsale 116 | # sold tokens: 30% 117 | # team tokens: 70% 118 | # 119 | token_distribution: 120 | contract_name: MysteriumTokenDistribution 121 | contract_file: MysteriumTokenDistribution.sol 122 | arguments: 123 | _token: "{{contracts.token.address}}" 124 | _crowdsale: "{{contracts.crowdsale.address}}" 125 | _mysteriumPricing: "{{contracts.pricing_strategy.address}}" 126 | 127 | # Post-deployment actions connect contracts together. 128 | post_actions: | 129 | 130 | # Enable new token minting by a crowdsale contract 131 | confirm_tx(token.transact({"from": deploy_address}).setMintAgent(crowdsale.address, True)) 132 | 133 | # Enable token mintint at the end of the crowdsale to create bonus pool, founder pool and such 134 | confirm_tx(token.transact({"from": deploy_address}).setMintAgent(token_distribution.address, True)) 135 | confirm_tx(crowdsale.transact({"from": deploy_address}).setFinalizeAgent(token_distribution.address)) 136 | 137 | # Manual release token transfer at the end of the crowdsale 138 | confirm_tx(token.transact({"from": deploy_address}).setReleaseAgent(deploy_address)) 139 | 140 | # Set token upgrade master to team multisig to give the new token path 141 | confirm_tx(token.transact({"from": deploy_address}).setUpgradeMaster(team_multisig_funds.address)) 142 | 143 | # Allow pricing strategy to read crowdsale for the CHF rate chec 144 | confirm_tx(pricing_strategy.transact({"from": deploy_address}).setCrowdsale(crowdsale.address)) 145 | 146 | # Allow test buys from these accounts before crowdsale begins 147 | confirm_multiple_txs( \ 148 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist(deploy_address, True), \ 149 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist("0x1c5e4db864861D9b6203bd86Af0C0B5ffcD6115d", True), \ 150 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist("0x73cd5cb24682239cF38732B3740a193cf6CE83ac", True), \ 151 | ) 152 | 153 | # Set token distribution model 154 | confirm_tx( \ 155 | token_distribution.transact({"from": deploy_address}).setVaults( \ 156 | _futureRoundVault=future_funding_vault.address, \ 157 | _foundationWallet=team_multisig_foundation.address, \ 158 | _teamVault=founders_vault.address, \ 159 | _seedVault1=seed_participant_vault.address, \ 160 | _seedVault2=seed_participant_vault_2.address, \ 161 | ) \ 162 | ) 163 | 164 | # 165 | # Set vault crowdsale data 166 | # 167 | confirm_multiple_txs( \ 168 | seed_participant_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 169 | seed_participant_vault_2.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 170 | future_funding_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 171 | founders_vault.transact({"from": deploy_address}).setCrowdsale(crowdsale.address), \ 172 | ) 173 | 174 | # 175 | # Load vault participant data 176 | # 177 | 178 | # Founders vault goes 100% to team multisig 179 | confirm_multiple_txs( \ 180 | founders_vault.transact({"from": deploy_address}).addInvestor(team_multisig_funds.address, 100), \ 181 | future_funding_vault.transact({"from": deploy_address}).addInvestor(team_multisig_foundation.address, 100), \ 182 | ) 183 | 184 | # Seed vault 1 185 | # TODO - using a test value as one address 186 | confirm_multiple_txs(*load_investor_data(seed_participant_vault, deploy_address, "fake_seed_investor_data.csv")) 187 | 188 | # Seed vault 2 189 | # TODO - using a test value as one address 190 | confirm_multiple_txs(*load_investor_data(seed_participant_vault_2, deploy_address, "fake_seed_investor_data.csv")) 191 | 192 | # Sanity check 193 | verify_actions: | 194 | assert token.call().owner().lower() == deploy_address.lower() 195 | assert crowdsale.call().owner().lower() == deploy_address.lower() 196 | assert crowdsale.call().multisigWallet().lower() == intermediate_vault.address.lower() 197 | assert token_distribution.call().isSane() 198 | assert crowdsale.call().getState() in (CrowdsaleState.PreFunding, CrowdsaleState.Funding) 199 | assert seed_participant_vault.call().investorCount() == 33, "Got investor count: {}".format(seed_participant_vault.call().investorCount()) 200 | assert seed_participant_vault_2.call().investorCount() == 33, "Got investor count: {}".format(seed_participant_vault_2.call().investorCount()) 201 | 202 | # As the last action, after successful deployment, set the right token symbol and name so that it shows in a blockchain explorer 203 | confirm_tx(token.transact({"from": deploy_address}).setTokenInformation("Xterium", "XXX")) 204 | 205 | 206 | -------------------------------------------------------------------------------- /crowdsales/mysterium-vaults-kovan.deployment-report.yml: -------------------------------------------------------------------------------- 1 | 2 | chain: kovan 3 | 4 | verify_on_etherscan: no 5 | 6 | # Firefox 53b has an issue that prevents correct Selenium automated 7 | # interaction with Etherscan form. 8 | # http://stackoverflow.com/q/43175013/315168 9 | browser_driver: chrome 10 | 11 | # 12 | # Contracts section defines different smart contracts that 13 | # are required to run the ICO. 14 | # 15 | contracts: 16 | 17 | seed_participant_vault: !!omap 18 | - contract_name: MultiVault 19 | - contract_file: MultiVault.sol 20 | - arguments: !!omap 21 | - _owner: '0x001FC7d7E506866aEAB82C11dA515E9DD6D02c25' 22 | - _freezeEndsAt: 1 23 | - address: '0x5d06eb01470e092bde03f64d6b7acfdb053add06' 24 | - constructor_args: '0x000000000000000000000000001fc7d7e506866aeab82c11da515e9dd6d02c250000000000000000000000000000000000000000000000000000000000000001' 25 | - libraries: 26 | SafeMathLib: '0x9d54f56c55663efd8e0d9f73f1d7c3a9330606fb' 27 | seed_participant_vault_2: !!omap 28 | - contract_name: MultiVault 29 | - contract_file: MultiVault.sol 30 | - arguments: !!omap 31 | - _owner: '0x001FC7d7E506866aEAB82C11dA515E9DD6D02c25' 32 | - _freezeEndsAt: 1528391820 33 | - address: '0x5d06eb01470e092bde03f64d6b7acfdb053add06' 34 | - constructor_args: '0x000000000000000000000000001fc7d7e506866aeab82c11da515e9dd6d02c25000000000000000000000000000000000000000000000000000000005b19688c' 35 | - libraries: 36 | SafeMathLib: '0x9d54f56c55663efd8e0d9f73f1d7c3a9330606fb' 37 | founders: !!omap 38 | - contract_name: MultiVault 39 | - contract_file: MultiVault.sol 40 | - arguments: !!omap 41 | - _owner: '0x001FC7d7E506866aEAB82C11dA515E9DD6D02c25' 42 | - _freezeEndsAt: 1528391864 43 | - address: '0x5d06eb01470e092bde03f64d6b7acfdb053add06' 44 | - constructor_args: '0x000000000000000000000000001fc7d7e506866aeab82c11da515e9dd6d02c25000000000000000000000000000000000000000000000000000000005b1968b8' 45 | - libraries: 46 | SafeMathLib: '0x9d54f56c55663efd8e0d9f73f1d7c3a9330606fb' 47 | deploy_address: '0x001FC7d7E506866aEAB82C11dA515E9DD6D02c25' 48 | -------------------------------------------------------------------------------- /crowdsales/mysterium-vaults-kovan.yml: -------------------------------------------------------------------------------- 1 | kovan: 2 | 3 | chain: kovan 4 | 5 | verify_on_etherscan: no 6 | 7 | # Firefox 53b has an issue that prevents correct Selenium automated 8 | # interaction with Etherscan form. 9 | # http://stackoverflow.com/q/43175013/315168 10 | browser_driver: chrome 11 | 12 | # 13 | # Contracts section defines different smart contracts that 14 | # are required to run the ICO. 15 | # 16 | contracts: 17 | 18 | seed_participant_vault: 19 | contract_name: MultiVault 20 | contract_file: MultiVault.sol 21 | arguments: 22 | _owner: "{{ deploy_address }}" 23 | _freezeEndsAt: "{{ 1 }}" # TODO 24 | 25 | seed_participant_vault_2: 26 | contract_name: MultiVault 27 | contract_file: MultiVault.sol 28 | arguments: 29 | _owner: "{{ deploy_address }}" 30 | _freezeEndsAt: "{{ time() + 12*30*24*3600 }}" # 12 months 31 | 32 | founders: 33 | contract_name: MultiVault 34 | contract_file: MultiVault.sol 35 | arguments: 36 | _owner: "{{ deploy_address }}" 37 | _freezeEndsAt: "{{ time() + 12*30*24*3600 }}" # 12 months 38 | -------------------------------------------------------------------------------- /crowdsales/mysterium-vaults-mainnet.deployment-report.yml: -------------------------------------------------------------------------------- 1 | 2 | chain: mainnet 3 | 4 | verify_on_etherscan: yes 5 | 6 | # Firefox 53b has an issue that prevents correct Selenium automated 7 | # interaction with Etherscan form. 8 | # http://stackoverflow.com/q/43175013/315168 9 | browser_driver: chrome 10 | 11 | # 12 | # Contracts section defines different smart contracts that 13 | # are required to run the ICO. 14 | # 15 | contracts: 16 | 17 | # seed_participant_vault: 18 | # contract_name: MultiVault 19 | # contract_file: MultiVault.sol 20 | # arguments: 21 | # _owner: "{{ deploy_address }}" 22 | # _freezeEndsAt: "{{ 1 }}" # TODO 23 | 24 | # seed_participant_vault_2: 25 | # contract_name: MultiVault 26 | # contract_file: MultiVault.sol 27 | # arguments: 28 | # _owner: "{{ deploy_address }}" 29 | # _freezeEndsAt: "{{ time() + 12*30*24*3600 }}" # 12 months 30 | 31 | founders: !!omap 32 | - contract_name: MultiVault 33 | - contract_file: MultiVault.sol 34 | - arguments: !!omap 35 | - _owner: '0x6efD5665ab4B345A7eBE63c679b651f375DDdB7E' 36 | - _freezeEndsAt: 1528397739 37 | - address: '0x33222541eae599124df510d02f6e70dada1a9331' 38 | - constructor_args: '0x0000000000000000000000006efd5665ab4b345a7ebe63c679b651f375dddb7e000000000000000000000000000000000000000000000000000000005b197fab' 39 | - libraries: 40 | SafeMathLib: '0x790d13e76b2c727086018842af947408e371868f' 41 | - etherscan_link: https://etherscan.io/address/0x33222541eae599124df510d02f6e70dada1a9331 42 | deploy_address: '0x6efD5665ab4B345A7eBE63c679b651f375DDdB7E' 43 | -------------------------------------------------------------------------------- /crowdsales/mysterium-vaults-mainnet.yml: -------------------------------------------------------------------------------- 1 | mainnet: 2 | 3 | chain: mainnet 4 | 5 | verify_on_etherscan: yes 6 | 7 | # Firefox 53b has an issue that prevents correct Selenium automated 8 | # interaction with Etherscan form. 9 | # http://stackoverflow.com/q/43175013/315168 10 | browser_driver: chrome 11 | 12 | # 13 | # Contracts section defines different smart contracts that 14 | # are required to run the ICO. 15 | # 16 | contracts: 17 | 18 | # seed_participant_vault: 19 | # contract_name: MultiVault 20 | # contract_file: MultiVault.sol 21 | # arguments: 22 | # _owner: "{{ deploy_address }}" 23 | # _freezeEndsAt: "{{ 1 }}" # TODO 24 | 25 | # seed_participant_vault_2: 26 | # contract_name: MultiVault 27 | # contract_file: MultiVault.sol 28 | # arguments: 29 | # _owner: "{{ deploy_address }}" 30 | # _freezeEndsAt: "{{ time() + 12*30*24*3600 }}" # 12 months 31 | 32 | founders: 33 | contract_name: MultiVault 34 | contract_file: MultiVault.sol 35 | arguments: 36 | _owner: "{{ deploy_address }}" 37 | _freezeEndsAt: "{{ time() + 12*30*24*3600 }}" # 12 months 38 | -------------------------------------------------------------------------------- /fake_seed_investor_data.csv: -------------------------------------------------------------------------------- 1 | 0x10a1c1cb95c92ec31d3f22c66eef1d9f3f258c6b,14.16666667 2 | 0x1db3439a222c519ab44bb1144fc28167b4fa6ee6,10 3 | 0xc8e99360d5b672d66610b0db0807611fe954ccf9,10 4 | 0x6df24f6685a62f791ba337bf3ff67e91f3d4bc3a,8.333333333 5 | 0xa51826379d5ff609371cd8f2dceb2d42357b1dbe,6.000213257 6 | 0x160cfd46043149ebb83221bd457f32b22513c462,5 7 | 0x6b79b52d6c99adbf247d459557a6cbae20ffa3c4,5 8 | 0x77419f7285d92427df5b81e14c32e4120f75c137,8.333300756 9 | 0x5fbd9b8615faece838e4c6bc7806a4270125faf5,4.165 10 | 0xc823f66b016d6eb4e3868756078362f7e004dbb5,4.034341851 11 | 0xA54E7B404dd32a8C57A929a6a2E1aFB396Fa3Cb1,3.333333333 12 | 0xbdc425e9cb0276a922326290ad723a6fbd60fd6d,3.333333333 13 | 0xe1ad30971b83c17e2a24c0334cb45f808abebc87,3.333333333 14 | 0xe238e7dcf6b7b54d434d23d0b3f537e946da973b,3.333333333 15 | 0xbd625ea5932796ea8516042305f7df0d2be8f9f3,2.199025229 16 | 0x92d5cc9c92e7b73a4df0ec93ca1cbd2612f25b59,2 17 | 0xd3b6958d847d9062e7c8e937aaac3e7331122397,1.666666667 18 | 0x8b7967b6af1026df6db54a103e0a699e7a08a618,1 19 | 0x663aee554d6d8e460c80fd170c1d365377a2cfb4,0.8333333333 20 | 0x4f4a9be10cd5d3fb5de48c17be296f895690645b,0.75 21 | 0x47696054e71e4c3f899119601a255a7065c3087b,0.5333333333 22 | 0x00deef53ea4b220086b0f5301b618404da2619c7,0.5 23 | 0x7bf6926bcf01507d5a5600f49d1650540cdaad48,0.4833333333 24 | 0xf68b1948b3637903cb43cf9807d4ca11b48d1845,0.4466666667 25 | 0xfacdd7f1b8a4bf2d881953e5751654d233024935,0.4 26 | 0xc046b59484843b2af6ca105afd88a3ab60e9b7cd,0.25 27 | 0x83156c79d8e9adf4dbc940c6a56dc58f5e5f8cd5,0.1666666667 28 | 0xee2e0e56e11fae260f3de0d6767fc1e970fc69d3,0.1666666667 29 | 0x8ccbac75b7e58493b5e38ce734f40a6b83050e07,0.08333333333 30 | 0xfcc7ee7fd7ce91b9ac5d52b4e06475db4c86f88b,0.04916666667 31 | 0xf243a67914182b262fbd28cab6d6d23d7f56a927,0.0445 32 | 0xdaf888c22e6e5cfd268ffbeec27cb87bf4e3bac4,0.03166666667 33 | 0xe045a076a77ab28157e88d1cffd12053f799ec44,0.02945223992 34 | -------------------------------------------------------------------------------- /founders_data.csv: -------------------------------------------------------------------------------- 1 | 0x9994Ed5db71980bABBad3C852a68EcB0e90Ea816,39.95819 2 | 0xB60a1cdCfAb9cA0B05787EC1EE2fc0fF573A0AEd,33.31404126 3 | 0x145908ED62433ebD8978Fed8d69aC42762f33d28,5 4 | 0x00994c7Be7fb486D2a5013D5E8B0b9305c74e618,9.705708707 5 | 0xa169B1153B88Cbe1c55F2493dA6C330F723DbaB5,1.182392202 6 | 0xdDf2bfa0aD3800877510aCAA767458051A07DACe,4.736158419 7 | 0xe8055ea1F2f38d2F795099E0a085F0CFAb42a878,3.226644255 8 | 0x0007013D71C0946126d404Fd44b3B9c97F3418e7,2.876865157 9 | -------------------------------------------------------------------------------- /helpers/before_crowdsale.py: -------------------------------------------------------------------------------- 1 | """Run before crowdsale to set Bitcoin Suisse address and final CHF rate.""" 2 | 3 | -------------------------------------------------------------------------------- /helpers/coins_calculator.py: -------------------------------------------------------------------------------- 1 | # hardcode constants to smart contract 2 | SOFT_CAP_CHF = 6000000 3 | MIN_SOFT_CAP_CHF = 2000000 4 | SEED_RAISED_ETH = 6000 5 | FOUNDATION_PERCENTAGE = 9 6 | TEAM_PERCENTAGE = 10 7 | EARLYBIRD_PRICE_MULTIPLIER = 1.2 8 | REGULAR_PRICE_MULTIPLIER = 1 9 | 10 | # set parameter before Token Sale 11 | eth_chf_price = 88 12 | 13 | # smart contract knows this value after raising is finished 14 | amount_raised_chf = 80000000 15 | 16 | 17 | 18 | # step 1, E6: Calculate "EarlyBird" coins (1chf = 1.2 myst), based on C10 19 | if amount_raised_chf <= SOFT_CAP_CHF: 20 | earlybird_coins = amount_raised_chf * EARLYBIRD_PRICE_MULTIPLIER 21 | else: 22 | earlybird_coins = SOFT_CAP_CHF * EARLYBIRD_PRICE_MULTIPLIER 23 | 24 | print 'Early Bird Coins: {}'.format(earlybird_coins) 25 | 26 | 27 | # step 2, F6: Calculate Regular investor coins (1chf = 1myst), based on C11 28 | regular_coins = 0 29 | if amount_raised_chf > SOFT_CAP_CHF: 30 | regular_coins = (amount_raised_chf - SOFT_CAP_CHF) * REGULAR_PRICE_MULTIPLIER 31 | 32 | print 'Regular Coins: {}'.format(regular_coins) 33 | 34 | 35 | # step 3, G5: Define Seed MULTIPLIER, based on C8 - raised amount during ICO 36 | # 2M - 1x 37 | # 6M - 5x 38 | if amount_raised_chf <= MIN_SOFT_CAP_CHF: 39 | seed_multiplier = 1 40 | elif amount_raised_chf > MIN_SOFT_CAP_CHF and amount_raised_chf < SOFT_CAP_CHF: 41 | seed_multiplier = amount_raised_chf / 1000000.0 - 1 42 | elif amount_raised_chf >= SOFT_CAP_CHF: 43 | seed_multiplier = 5 44 | 45 | print 'Seed Multiplier: {}x'.format(seed_multiplier) 46 | 47 | 48 | # step 4, G6: Calculate Seed Round Tokens using G5 49 | seed_coins = SEED_RAISED_ETH * eth_chf_price * seed_multiplier 50 | print 'Seed Coins: {}'.format(seed_coins) 51 | 52 | 53 | # step 5, H3: Calculate PERCENTAGE of "tokens reserved for II'nd round", using C8 54 | # 2M - 50% 55 | # 6M - 15% 56 | if amount_raised_chf <= MIN_SOFT_CAP_CHF: 57 | future_round_percentage = 50 58 | elif amount_raised_chf > MIN_SOFT_CAP_CHF and amount_raised_chf < SOFT_CAP_CHF: 59 | # calculate proportionally 60 | # y = 67.5 - 8.75x 61 | future_round_percentage = 67.5 - 8.75 * (amount_raised_chf / 1000000.0) 62 | elif amount_raised_chf >= SOFT_CAP_CHF: 63 | future_round_percentage = 15 64 | 65 | print 'Future reserved Coins percentage: {}%'.format(future_round_percentage) 66 | 67 | 68 | # step 6, E2: Calculate PERENTAGE of (Early bird + Regular + Seed) = 100% - Team(10%) - Foundation (9%) - II'nd round(H3) 69 | percentage_of_three = 100 - FOUNDATION_PERCENTAGE - TEAM_PERCENTAGE - future_round_percentage 70 | print 'Percentage of (Early bird + Regular + Seed): {}%'.format(percentage_of_three) 71 | 72 | 73 | # step 7, E3,knowing total percentage E2 and total coins for (EarlyBird, Regular and Seed) = sum(E3:G3) 74 | earlybird_percentage = earlybird_coins * percentage_of_three / (earlybird_coins+regular_coins+seed_coins) 75 | print 'Early bird percentage: {}%'.format(earlybird_percentage) 76 | 77 | 78 | # step 8, K6: Calculate TOTAL coins knowing E3 & E6 79 | total_coins = earlybird_coins * 100 / earlybird_percentage 80 | print 'Total coins: {}'.format(total_coins) 81 | 82 | 83 | # step 9, H6: Calculate II'nd round coins using K6 and H3 84 | future_round_coins = future_round_percentage * total_coins / 100 85 | print 'Future round coins: {}'.format(future_round_coins) 86 | 87 | 88 | # step 10, I6: Calculate Foundation coins using K6 and I3 89 | foundation_coins = FOUNDATION_PERCENTAGE * total_coins / 100 90 | print 'Foundation coins: {}'.format(foundation_coins) 91 | 92 | 93 | # step 11, J6: Calculate Team coins using K6 and J3 94 | team_coins = TEAM_PERCENTAGE * total_coins / 100 95 | print 'Team coins: {}'.format(team_coins) 96 | 97 | 98 | 99 | ######################## 100 | print 101 | 102 | 103 | 104 | # seed coins to vault1 (no-lock) 1x 105 | vault1 = seed_coins / seed_multiplier 106 | print 'Vault1 seed coins (no-lock): {}'.format(vault1) 107 | 108 | 109 | # seed coins to vault2 (with-lock) above 1x 110 | vault2 = seed_coins - seed_coins / seed_multiplier 111 | print 'Vault2 seed coins (with-lock): {}'.format(vault2) 112 | 113 | -------------------------------------------------------------------------------- /install_solc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Install solc 4 | # 5 | 6 | set -e 7 | set -u 8 | 9 | mkdir -p solc-versions/solc-$SOLC_VERSION 10 | cd solc-versions/solc-$SOLC_VERSION 11 | git clone --recurse-submodules --branch v$SOLC_VERSION --depth 50 https://github.com/ethereum/solidity.git 12 | ./solidity/scripts/install_deps.sh 13 | wget https://github.com/ethereum/solidity/releases/download/v$SOLC_VERSION/solidity-ubuntu-trusty.zip 14 | unzip solidity-ubuntu-trusty.zip 15 | echo "Solidity installed at $TRAVIS_BUILD_DIR/solc-versions/solc-$SOLC_VERSION/solc" 16 | tree $TRAVIS_BUILD_DIR/solc-versions/solc-$SOLC_VERSION 17 | -------------------------------------------------------------------------------- /populus.json: -------------------------------------------------------------------------------- 1 | { 2 | "chains": { 3 | "mainnet": { 4 | "chain": { 5 | "class": "populus.chain.external.ExternalChain" 6 | }, 7 | "contracts": { 8 | "backends": { 9 | "JSONFile": { 10 | "$ref": "contracts.backends.JSONFile" 11 | }, 12 | "Memory": { 13 | "$ref": "contracts.backends.Memory" 14 | }, 15 | "ProjectContracts": { 16 | "$ref": "contracts.backends.ProjectContracts" 17 | }, 18 | "TestContracts": { 19 | "$ref": "contracts.backends.TestContracts" 20 | } 21 | } 22 | }, 23 | "web3": { 24 | "provider": { 25 | "class": "web3.providers.rpc.HTTPProvider", 26 | "settings": { 27 | "endpoint_uri": "http://127.0.0.1:8545", 28 | "request_kwargs": { 29 | "timeout": 180 30 | } 31 | } 32 | } 33 | } 34 | }, 35 | "kovan": { 36 | "chain": { 37 | "class": "populus.chain.external.ExternalChain" 38 | }, 39 | "contracts": { 40 | "backends": { 41 | "JSONFile": { 42 | "$ref": "contracts.backends.JSONFile" 43 | }, 44 | "Memory": { 45 | "$ref": "contracts.backends.Memory" 46 | }, 47 | "ProjectContracts": { 48 | "$ref": "contracts.backends.ProjectContracts" 49 | }, 50 | "TestContracts": { 51 | "$ref": "contracts.backends.TestContracts" 52 | } 53 | } 54 | }, 55 | "web3": { 56 | "provider": { 57 | "class": "web3.providers.rpc.HTTPProvider", 58 | "settings": { 59 | "endpoint_uri": "http://127.0.0.1:8547", 60 | "request_kwargs": { 61 | "timeout": 180 62 | } 63 | } 64 | } 65 | } 66 | }, 67 | "ropsten": { 68 | "chain": { 69 | "class": "populus.chain.geth.TestnetChain", 70 | "timeout": 240 71 | }, 72 | "contracts": { 73 | "backends": { 74 | "JSONFile": { 75 | "$ref": "contracts.backends.JSONFile" 76 | }, 77 | "Memory": { 78 | "$ref": "contracts.backends.Memory" 79 | }, 80 | "ProjectContracts": { 81 | "$ref": "contracts.backends.ProjectContracts" 82 | }, 83 | "TestContracts": { 84 | "$ref": "contracts.backends.TestContracts" 85 | } 86 | } 87 | }, 88 | "web3": { 89 | "provider": { 90 | "class": "web3.providers.rpc.HTTPProvider", 91 | "settings": { 92 | "endpoint_uri": "http://127.0.0.1:8546", 93 | "request_kwargs": { 94 | "timeout": 250 95 | } 96 | } 97 | } 98 | } 99 | }, 100 | "temp": { 101 | "chain": { 102 | "class": "populus.chain.geth.TemporaryGethChain" 103 | }, 104 | "contracts": { 105 | "backends": { 106 | "Memory": { 107 | "$ref": "contracts.backends.Memory" 108 | }, 109 | "ProjectContracts": { 110 | "$ref": "contracts.backends.ProjectContracts" 111 | }, 112 | "TestContracts": { 113 | "$ref": "contracts.backends.TestContracts" 114 | } 115 | } 116 | }, 117 | "web3": { 118 | "$ref": "web3.GethIPC" 119 | } 120 | }, 121 | "tester": { 122 | "chain": { 123 | "class": "populus.chain.tester.TesterChain" 124 | }, 125 | "contracts": { 126 | "backends": { 127 | "Memory": { 128 | "$ref": "contracts.backends.Memory" 129 | }, 130 | "ProjectContracts": { 131 | "$ref": "contracts.backends.ProjectContracts" 132 | }, 133 | "TestContracts": { 134 | "$ref": "contracts.backends.TestContracts" 135 | } 136 | } 137 | }, 138 | "web3": { 139 | "$ref": "web3.Tester" 140 | } 141 | }, 142 | "testrpc": { 143 | "chain": { 144 | "class": "populus.chain.testrpc.TestRPCChain" 145 | }, 146 | "contracts": { 147 | "backends": { 148 | "Memory": { 149 | "$ref": "contracts.backends.Memory" 150 | }, 151 | "ProjectContracts": { 152 | "$ref": "contracts.backends.ProjectContracts" 153 | }, 154 | "TestContracts": { 155 | "$ref": "contracts.backends.TestContracts" 156 | } 157 | } 158 | }, 159 | "web3": { 160 | "$ref": "web3.TestRPC" 161 | } 162 | } 163 | }, 164 | "compilation": { 165 | "contracts_source_dir": "./contracts", 166 | "settings": { 167 | "optimize": true, 168 | "import_remappings": ["zeppelin=zeppelin", "ico=ico"] 169 | } 170 | }, 171 | "contracts": { 172 | "backends": { 173 | "JSONFile": { 174 | "class": "populus.contracts.backends.filesystem.JSONFileBackend", 175 | "priority": 10, 176 | "settings": { 177 | "file_path": "./registrar.json" 178 | } 179 | }, 180 | "Memory": { 181 | "class": "populus.contracts.backends.memory.MemoryBackend", 182 | "priority": 50 183 | }, 184 | "ProjectContracts": { 185 | "class": "populus.contracts.backends.project.ProjectContractsBackend", 186 | "priority": 20 187 | }, 188 | "TestContracts": { 189 | "class": "populus.contracts.backends.testing.TestContractsBackend", 190 | "priority": 40 191 | } 192 | } 193 | }, 194 | "version": "3", 195 | "web3": { 196 | "GethIPC": { 197 | "provider": { 198 | "class": "web3.providers.ipc.IPCProvider" 199 | } 200 | }, 201 | "InfuraMainnet": { 202 | "eth": { 203 | "default_account": "0x0000000000000000000000000000000000000001" 204 | }, 205 | "provider": { 206 | "class": "web3.providers.rpc.HTTPProvider", 207 | "settings": { 208 | "endpoint_uri": "https://mainnet.infura.io" 209 | } 210 | } 211 | }, 212 | "InfuraRopsten": { 213 | "eth": { 214 | "default_account": "0x0000000000000000000000000000000000000001" 215 | }, 216 | "provider": { 217 | "class": "web3.providers.rpc.HTTPProvider", 218 | "settings": { 219 | "endpoint_uri": "https://ropsten.infura.io" 220 | } 221 | } 222 | }, 223 | "TestRPC": { 224 | "provider": { 225 | "class": "web3.providers.tester.TestRPCProvider" 226 | } 227 | }, 228 | "Tester": { 229 | "provider": { 230 | "class": "web3.providers.tester.EthereumTesterProvider" 231 | } 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | # Don't scan venv folder or other folders 3 | addopts = tests 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyconfig==0.9.1 2 | argh==0.26.2 3 | bitcoin==1.1.42 4 | cffi==1.9.1 5 | click==6.7 6 | contextlib2==0.5.4 7 | eth-testrpc==1.1.1 8 | ethereum==1.6.1 9 | ethereum-abi-utils==0.4.0 10 | ethereum-utils==0.2.0 11 | json-rpc==1.10.3 12 | jsonschema==2.6.0 13 | pathtools==0.1.2 14 | pbkdf2==1.3 15 | populus==1.6.8 16 | py==1.4.33 17 | py-geth==1.7.1 18 | py-solc==1.0.1 19 | pycparser==2.17 20 | pycryptodome==3.4.5 21 | pyethash==0.1.27 22 | pylru==1.0.9 23 | pysha3==1.0.2 24 | pytest==3.0.7 25 | PyYAML==3.12 26 | repoze.lru==0.6 27 | requests==2.13.0 28 | ruamel.yaml==0.14.5 29 | rlp==0.4.6 30 | scrypt==0.8.0 31 | secp256k1==0.13.2 32 | toposort==1.5 33 | watchdog==0.8.3 34 | web3==3.7.2 35 | Werkzeug==0.12.1 36 | Sphinx==1.5.3 37 | -------------------------------------------------------------------------------- /seed_investor_data.csv: -------------------------------------------------------------------------------- 1 | 0x59795fccfbe2f166651c6b73d10a7a1522a0ed89,14.16666667 2 | 0x1db3439a222c519ab44bb1144fc28167b4fa6ee6,10 3 | 0xc8e99360d5b672d66610b0db0807611fe954ccf9,10 4 | 0x6df24f6685a62f791ba337bf3ff67e91f3d4bc3a,8.333333333 5 | 0xa51826379d5ff609371cd8f2dceb2d42357b1dbe,6.000213257 6 | 0x160cfd46043149ebb83221bd457f32b22513c462,5 7 | 0x6b79b52d6c99adbf247d459557a6cbae20ffa3c4,5 8 | 0x77419f7285d92427df5b81e14c32e4120f75c137,8.333300756 9 | 0x5fbd9b8615faece838e4c6bc7806a4270125faf5,4.165 10 | 0xc823f66b016d6eb4e3868756078362f7e004dbb5,4.034341851 11 | 0xA54E7B404dd32a8C57A929a6a2E1aFB396Fa3Cb1,3.333333333 12 | 0xbdc425e9cb0276a922326290ad723a6fbd60fd6d,3.333333333 13 | 0xe1ad30971b83c17e2a24c0334cb45f808abebc87,3.333333333 14 | 0xe238e7dcf6b7b54d434d23d0b3f537e946da973b,3.333333333 15 | 0xbd625ea5932796ea8516042305f7df0d2be8f9f3,2.199025229 16 | 0x92d5cc9c92e7b73a4df0ec93ca1cbd2612f25b59,2 17 | 0xd3b6958d847d9062e7c8e937aaac3e7331122397,1.666666667 18 | 0x8b7967b6af1026df6db54a103e0a699e7a08a618,1 19 | 0x663aee554d6d8e460c80fd170c1d365377a2cfb4,0.8333333333 20 | 0x4f4a9be10cd5d3fb5de48c17be296f895690645b,0.75 21 | 0x47696054e71e4c3f899119601a255a7065c3087b,0.5333333333 22 | 0x00deef53ea4b220086b0f5301b618404da2619c7,0.5 23 | 0x7bf6926bcf01507d5a5600f49d1650540cdaad48,0.4833333333 24 | 0xf68b1948b3637903cb43cf9807d4ca11b48d1845,0.4466666667 25 | 0xfacdd7f1b8a4bf2d881953e5751654d233024935,0.4 26 | 0xc046b59484843b2af6ca105afd88a3ab60e9b7cd,0.25 27 | 0x83156c79d8e9adf4dbc940c6a56dc58f5e5f8cd5,0.1666666667 28 | 0xee2e0e56e11fae260f3de0d6767fc1e970fc69d3,0.1666666667 29 | 0x8ccbac75b7e58493b5e38ce734f40a6b83050e07,0.08333333333 30 | 0xfcc7ee7fd7ce91b9ac5d52b4e06475db4c86f88b,0.04916666667 31 | 0xf243a67914182b262fbd28cab6d6d23d7f56a927,0.0445 32 | 0xdaf888c22e6e5cfd268ffbeec27cb87bf4e3bac4,0.03166666667 33 | 0xe045a076a77ab28157e88d1cffd12053f799ec44,0.02945223992 34 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Test fixtures.""" 2 | import datetime 3 | 4 | import pytest 5 | from web3.contract import Contract 6 | 7 | 8 | from ico.tests.utils import time_travel 9 | from ico.state import CrowdsaleState 10 | from ico.tests.fixtures.general import * # noqa 11 | from ico.tests.fixtures.flatprice import * # noqa 12 | from ico.tests.fixtures.releasable import * # noqa 13 | from ico.tests.fixtures.finalize import * # noqa 14 | from ico.tests.fixtures.presale import * # noqa 15 | 16 | 17 | @pytest.fixture 18 | def starts_at() -> int: 19 | """When pre-ico opens""" 20 | return int(datetime.datetime(2017, 1, 1).timestamp()) 21 | 22 | 23 | @pytest.fixture 24 | def ends_at() -> int: 25 | """When pre-ico closes""" 26 | return int(datetime.datetime(2017, 2, 3).timestamp()) 27 | 28 | 29 | @pytest.fixture 30 | def mysterium_token(chain, team_multisig, token_name, token_symbol, initial_supply) -> Contract: 31 | """Create the token contract.""" 32 | 33 | args = ["Mysterium", "MYST", 0, 8] # Owner set 34 | 35 | tx = { 36 | "from": team_multisig 37 | } 38 | 39 | contract, hash = chain.provider.deploy_contract('MysteriumToken', deploy_args=args, deploy_transaction=tx) 40 | return contract 41 | 42 | 43 | @pytest.fixture 44 | def mysterium_finalize_agent(team_multisig, chain, mysterium_token, crowdsale, mysterium_pricing, accounts) -> Contract: 45 | # Create finalizer contract 46 | args = [ 47 | mysterium_token.address, 48 | crowdsale.address, 49 | mysterium_pricing.address 50 | ] 51 | tx = { 52 | "from": team_multisig 53 | } 54 | contract, hash = chain.provider.deploy_contract('MysteriumTokenDistribution', deploy_args=args, deploy_transaction=tx) 55 | 56 | contract.transact({"from": team_multisig}).setVaults( 57 | _futureRoundVault=accounts[0], 58 | _foundationWallet=accounts[0], 59 | _teamVault=accounts[0], 60 | _seedVault1=accounts[0], 61 | _seedVault2=accounts[0]) # TODO: Use actual contracts here 62 | return contract 63 | 64 | 65 | @pytest.fixture 66 | def mysterium_pricing(chain, preico_token_price, team_multisig) -> Contract: 67 | """Flat pricing contact.""" 68 | args = [ 69 | 120000 # 120 CHF = 1 ETH at the scale of 10000 70 | ] 71 | tx = { 72 | "from": team_multisig, 73 | } 74 | pricing_strategy, hash = chain.provider.deploy_contract('MysteriumPricing', deploy_args=args, deploy_transaction=tx) 75 | return pricing_strategy 76 | 77 | 78 | @pytest.fixture 79 | def crowdsale(chain, mysterium_token, mysterium_pricing, starts_at, ends_at, preico_funding_goal, team_multisig) -> Contract: 80 | token = mysterium_token 81 | 82 | args = [ 83 | token.address, 84 | mysterium_pricing.address, 85 | team_multisig, 86 | starts_at, 87 | ends_at, 88 | ] 89 | tx = { 90 | "from": team_multisig, 91 | } 92 | contract, hash = chain.provider.deploy_contract('MysteriumCrowdsale', deploy_args=args, deploy_transaction=tx) 93 | 94 | mysterium_pricing.transact({"from": team_multisig}).setCrowdsale(contract.address) 95 | mysterium_pricing.transact({"from": team_multisig}).setConversionRate(120*10000) 96 | 97 | assert contract.call().owner() == team_multisig 98 | assert not token.call().released() 99 | 100 | # Allow pre-ico contract to do mint() 101 | token.transact({"from": team_multisig}).setMintAgent(contract.address, True) 102 | token.transact({"from": team_multisig}).setTransferAgent(contract.address, True) 103 | assert token.call().mintAgents(contract.address) == True 104 | 105 | return contract 106 | 107 | 108 | @pytest.fixture() 109 | def ready_crowdsale(crowdsale, mysterium_token, mysterium_finalize_agent, team_multisig): 110 | """Crowdsale waiting the time to start.""" 111 | crowdsale.transact({"from": team_multisig}).setFinalizeAgent(mysterium_finalize_agent.address) # Must be done before sending 112 | mysterium_token.transact({"from": team_multisig}).setReleaseAgent(team_multisig) 113 | mysterium_token.transact({"from": team_multisig}).setTransferAgent(mysterium_finalize_agent.address, True) 114 | mysterium_token.transact({"from": team_multisig}).setMintAgent(mysterium_finalize_agent.address, True) 115 | return crowdsale 116 | 117 | 118 | @pytest.fixture() 119 | def started_crowdsale(chain, ready_crowdsale): 120 | """Crowdsale that has been time traveled to funding state.""" 121 | # Setup crowdsale to Funding state 122 | crowdsale = ready_crowdsale 123 | 124 | time_travel(chain, crowdsale.call().startsAt() + 1) 125 | assert crowdsale.call().getState() == CrowdsaleState.Funding 126 | return crowdsale 127 | -------------------------------------------------------------------------------- /tests/fixtures/finalize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from web3.contract import Contract 3 | 4 | 5 | @pytest.fixture() 6 | def default_finalize_agent(chain, uncapped_token, uncapped_flatprice) -> Contract: 7 | 8 | # Create finalizer contract 9 | args = [ 10 | uncapped_token.address, 11 | uncapped_flatprice.address, 12 | ] 13 | contract, hash = chain.provider.deploy_contract('DefaultFinalizeAgent', deploy_args=args) 14 | return contract 15 | 16 | 17 | @pytest.fixture 18 | def uncapped_flatprice_final(chain, uncapped_token, uncapped_flatprice, team_multisig, default_finalize_agent) -> Contract: 19 | """A ICO contract where we have a default finalizer in place.""" 20 | 21 | # Crowdsale calls this finalizer at the success 22 | uncapped_flatprice.transact({"from": team_multisig}).setFinalizeAgent(default_finalize_agent.address) 23 | 24 | # Token allows finalizer to release the tokens 25 | uncapped_token.transact({"from": team_multisig}).setReleaseAgent(default_finalize_agent.address) 26 | 27 | return uncapped_flatprice 28 | -------------------------------------------------------------------------------- /tests/fixtures/flatprice.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pytest 4 | from eth_utils import to_wei 5 | from web3.contract import Contract 6 | 7 | from ico.tests.utils import time_travel 8 | from ico.state import CrowdsaleState 9 | 10 | 11 | @pytest.fixture 12 | def preico_starts_at() -> int: 13 | """When pre-ico opens""" 14 | return int(datetime.datetime(2017, 1, 1).timestamp()) 15 | 16 | 17 | @pytest.fixture 18 | def preico_ends_at() -> int: 19 | """When pre-ico closes""" 20 | return int(datetime.datetime(2017, 1, 3).timestamp()) 21 | 22 | 23 | @pytest.fixture 24 | def preico_token_price() -> int: 25 | """Tokens per ether""" 26 | return to_wei(1, "ether") // 1200 27 | 28 | 29 | @pytest.fixture 30 | def preico_funding_goal() -> int: 31 | """Pre-ico funding goal is 1000 ETH.""" 32 | return to_wei(1000, "ether") 33 | 34 | 35 | @pytest.fixture 36 | def preico_cap() -> int: 37 | """Pre-ico funding goal is 1000 ETH.""" 38 | return to_wei(5000, "ether") 39 | 40 | 41 | @pytest.fixture 42 | def preico_token_allocation(token) -> int: 43 | """How many tokens we have allocated to be sold in pre-ico.""" 44 | return int(token.call().totalSupply() * 0.1) 45 | 46 | 47 | @pytest.fixture 48 | def flat_pricing(chain, preico_token_price) -> Contract: 49 | """Flat pricing contact.""" 50 | args = [ 51 | preico_token_price, 52 | ] 53 | pricing_strategy, hash = chain.provider.deploy_contract('FlatPricing', deploy_args=args) 54 | return pricing_strategy 55 | 56 | 57 | @pytest.fixture 58 | def uncapped_token(empty_token): 59 | return empty_token 60 | 61 | 62 | @pytest.fixture 63 | def uncapped_flatprice(chain, team_multisig, preico_starts_at, preico_ends_at, flat_pricing, preico_cap, preico_funding_goal, preico_token_allocation, uncapped_token) -> Contract: 64 | """Create a Pre-ICO crowdsale contract.""" 65 | 66 | token = uncapped_token 67 | 68 | args = [ 69 | token.address, 70 | flat_pricing.address, 71 | team_multisig, 72 | preico_starts_at, 73 | preico_ends_at, 74 | preico_funding_goal, 75 | ] 76 | 77 | tx = { 78 | "from": team_multisig, 79 | } 80 | 81 | contract, hash = chain.provider.deploy_contract('UncappedCrowdsale', deploy_args=args, deploy_transaction=tx) 82 | 83 | assert contract.call().owner() == team_multisig 84 | assert not token.call().released() 85 | 86 | # Allow pre-ico contract to do mint() 87 | token.transact({"from": team_multisig}).setMintAgent(contract.address, True) 88 | assert token.call().mintAgents(contract.address) == True 89 | 90 | return contract 91 | 92 | 93 | @pytest.fixture 94 | def uncapped_flatprice_goal_reached(chain, uncapped_flatprice, uncapped_flatprice_finalizer, preico_funding_goal, preico_starts_at, customer) -> Contract: 95 | """A ICO contract where the minimum funding goal has been reached.""" 96 | time_travel(chain, preico_starts_at + 1) 97 | wei_value = preico_funding_goal 98 | uncapped_flatprice.transact({"from": customer, "value": wei_value}).buy() 99 | return uncapped_flatprice 100 | 101 | 102 | 103 | @pytest.fixture() 104 | def uncapped_flatprice_finalizer(chain, presale_crowdsale, uncapped_token, team_multisig) -> Contract: 105 | """Set crowdsale end strategy.""" 106 | 107 | # Create finalizer contract 108 | args = [ 109 | uncapped_token.address, 110 | presale_crowdsale.address, 111 | ] 112 | contract, hash = chain.provider.deploy_contract('DefaultFinalizeAgent', deploy_args=args) 113 | uncapped_token.transact({"from": team_multisig}).setReleaseAgent(contract.address) 114 | presale_crowdsale.transact({"from": team_multisig}).setFinalizeAgent(contract.address) 115 | assert presale_crowdsale.call().getState() == CrowdsaleState.PreFunding 116 | return contract 117 | -------------------------------------------------------------------------------- /tests/fixtures/general.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from web3.contract import Contract 3 | 4 | 5 | @pytest.fixture 6 | def token_name() -> str: 7 | return "Unit test token" 8 | 9 | 10 | @pytest.fixture 11 | def token_symbol() -> str: 12 | return "TEST" 13 | 14 | 15 | @pytest.fixture 16 | def initial_supply() -> str: 17 | return 10000000 18 | 19 | 20 | @pytest.fixture 21 | def customer(accounts) -> str: 22 | """Get a customer address.""" 23 | return accounts[1] 24 | 25 | 26 | @pytest.fixture 27 | def customer_2(accounts) -> str: 28 | """Get another customer address.""" 29 | return accounts[2] 30 | 31 | @pytest.fixture 32 | def beneficiary(accounts) -> str: 33 | """The team control address.""" 34 | return accounts[3] 35 | 36 | 37 | @pytest.fixture 38 | def team_multisig(accounts) -> str: 39 | """The team multisig address.""" 40 | return accounts[4] 41 | 42 | 43 | @pytest.fixture 44 | def malicious_address(accounts) -> str: 45 | """Somebody who tries to perform activities they are not allowed to.""" 46 | return accounts[5] 47 | 48 | 49 | @pytest.fixture 50 | def empty_address(accounts): 51 | """This account never holds anything.""" 52 | return accounts[6] 53 | 54 | 55 | @pytest.fixture 56 | def allowed_party(accounts): 57 | """Gets ERC-20 allowance.""" 58 | return accounts[7] 59 | 60 | 61 | 62 | # 63 | # ERC-20 fixtures 64 | # 65 | 66 | @pytest.fixture 67 | def token_owner(beneficiary): 68 | return beneficiary 69 | 70 | 71 | @pytest.fixture 72 | def token(chain, team_multisig, token_name, token_symbol, initial_supply) -> Contract: 73 | """Create the token contract.""" 74 | 75 | args = [token_name, token_symbol, initial_supply, 0] # Owner set 76 | 77 | tx = { 78 | "from": team_multisig 79 | } 80 | 81 | contract, hash = chain.provider.deploy_contract('CrowdsaleToken', deploy_args=args, deploy_transaction=tx) 82 | return contract 83 | 84 | 85 | @pytest.fixture 86 | def empty_token(chain, team_multisig, token_name, token_symbol) -> Contract: 87 | """Create the token contract without initial supply.""" 88 | 89 | args = [token_name, token_symbol, 0, 0] # Owner set 90 | 91 | tx = { 92 | "from": team_multisig 93 | } 94 | 95 | contract, hash = chain.provider.deploy_contract('CrowdsaleToken', deploy_args=args, deploy_transaction=tx) 96 | return contract 97 | -------------------------------------------------------------------------------- /tests/fixtures/presale.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pytest 4 | from eth_utils import to_wei 5 | from web3.contract import Contract 6 | 7 | 8 | @pytest.fixture 9 | def presale_freeze_ends_at() -> int: 10 | """How long presale funds stay frozen until refund.""" 11 | return int(datetime.datetime(2017, 1, 1).timestamp()) 12 | 13 | 14 | @pytest.fixture 15 | def presale_fund_collector(chain, presale_freeze_ends_at, team_multisig) -> Contract: 16 | """In actual ICO, the price is doubled (for testing purposes).""" 17 | args = [ 18 | team_multisig, 19 | presale_freeze_ends_at, 20 | to_wei(1, "ether") 21 | ] 22 | tx = { 23 | "from": team_multisig, 24 | } 25 | presale_fund_collector, hash = chain.provider.deploy_contract('PresaleFundCollector', deploy_args=args, deploy_transaction=tx) 26 | return presale_fund_collector 27 | 28 | 29 | @pytest.fixture 30 | def presale_crowdsale(chain, presale_fund_collector, uncapped_flatprice, team_multisig): 31 | """ICO associated with the presale where funds will be moved to a presale.""" 32 | presale_fund_collector.transact({"from": team_multisig}).setCrowdsale(uncapped_flatprice.address) 33 | return uncapped_flatprice 34 | -------------------------------------------------------------------------------- /tests/fixtures/releasable.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pytest 4 | from eth_utils import to_wei 5 | from web3.contract import Contract 6 | 7 | 8 | @pytest.fixture 9 | def release_agent(chain, team_multisig, token) -> Contract: 10 | """Create a simple release agent (useful for testing).""" 11 | 12 | args = [token.address] 13 | 14 | tx = { 15 | "from": team_multisig 16 | } 17 | 18 | contract, hash = chain.provider.deploy_contract('SimpleReleaseAgent', deploy_args=args, deploy_transaction=tx) 19 | return contract 20 | 21 | 22 | @pytest.fixture 23 | def released_token(chain, team_multisig, token, release_agent, customer) -> Contract: 24 | """Create a Crowdsale token where transfer restrictions have been lifted.""" 25 | 26 | token.transact({"from": team_multisig}).setReleaseAgent(release_agent.address) 27 | release_agent.transact({"from": team_multisig}).release() 28 | 29 | # Make sure customer 1 has some token balance 30 | token.transact({"from": team_multisig}).transfer(customer, 10000) 31 | 32 | return token 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/test_crowdsale.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pytest 4 | from decimal import Decimal 5 | 6 | from eth_utils import from_wei 7 | from eth_utils import to_wei 8 | from ethereum.tester import TransactionFailed 9 | from web3.contract import Contract 10 | 11 | 12 | from ico.tests.utils import time_travel 13 | from ico.state import CrowdsaleState 14 | from ico.utils import decimalize_token_amount 15 | 16 | 17 | def in_chf(wei): 18 | """Convert amount to CHF using our test 120 rate.""" 19 | return int(from_wei(wei, "ether") * 120) 20 | 21 | 22 | @pytest.fixture 23 | def bitcoin_suisse(accounts) -> str: 24 | """Fixture for BitcoinSuisse address.""" 25 | return accounts[9] 26 | 27 | 28 | def test_caps(crowdsale, mysterium_pricing): 29 | """"Soft cap and hard cap match specification.""" 30 | 31 | # We lose ~1 ETH precision in the conversions 32 | assert abs(in_chf(crowdsale.call().getMinimumFundingGoal()) - 700000) < 10 33 | 34 | soft_cap = mysterium_pricing.call().getSoftCapInWeis() 35 | assert abs(in_chf(soft_cap) - 6000000) < 10 36 | 37 | 38 | def test_soft_cap_price(crowdsale, mysterium_pricing): 39 | """"Soft cap prices match the specification.""" 40 | 41 | one_chf_in_eth = to_wei(1/120, "ether") 42 | 43 | # See we get correct price for one token 44 | tokens_bought = mysterium_pricing.call().calculatePrice(one_chf_in_eth, 0, 0, '0x0000000000000000000000000000000000000000', 8) 45 | 46 | assert tokens_bought == 1 * 10**8 47 | 48 | 49 | def test_hard_cap_price(crowdsale, mysterium_pricing): 50 | """"Soft cap prices match the specification.""" 51 | 52 | one_point_two_chf_in_eth = to_wei(1.2/120, "ether") 53 | 54 | soft_cap_goal = mysterium_pricing.call().getSoftCapInWeis() 55 | 56 | # See we get correct price for one token 57 | tokens_bought = mysterium_pricing.call().calculatePrice(one_point_two_chf_in_eth, soft_cap_goal + 1, 0, '0x0000000000000000000000000000000000000000', 8) 58 | 59 | assert tokens_bought == 1 * 10**8 60 | 61 | 62 | def test_set_rate(crowdsale, mysterium_pricing, team_multisig): 63 | """"Set CHF rate before crowdsale begins.""" 64 | 65 | assert mysterium_pricing.call().chfRate() == 120 * 10000 66 | mysterium_pricing.transact({"from": team_multisig}).setConversionRate(130 * 10000) 67 | assert mysterium_pricing.call().chfRate() == 130 * 10000 68 | 69 | 70 | def test_set_rate_late(chain, crowdsale, mysterium_pricing, team_multisig): 71 | """"CHF rate cannot be set after the crowdsale starts..""" 72 | 73 | time_travel(chain, crowdsale.call().startsAt()+1) 74 | with pytest.raises(TransactionFailed): 75 | mysterium_pricing.transact({"from": team_multisig}).setConversionRate(130 * 10000) 76 | 77 | 78 | def test_bitcoin_suisse(chain, ready_crowdsale, bitcoin_suisse, mysterium_pricing, team_multisig): 79 | """"Test BitcoinSuisse address whitelisting. 80 | 81 | Spec 3.2. 82 | """ 83 | 84 | crowdsale = ready_crowdsale 85 | 86 | # Cannot transact initially 87 | assert crowdsale.call().getState() == CrowdsaleState.PreFunding 88 | 89 | with pytest.raises(TransactionFailed): 90 | crowdsale.transact({"from": bitcoin_suisse, "value": to_wei(10000, "ether")}).buy() 91 | 92 | # Now let's set rate and whitelist 93 | mysterium_pricing.transact({"from": team_multisig}).setConversionRate(130 * 10000) 94 | crowdsale.transact({"from": team_multisig}).setEarlyParicipantWhitelist(bitcoin_suisse, True) 95 | 96 | # Now BitcoinSuisse can execute 97 | crowdsale.transact({"from": bitcoin_suisse, "value": to_wei(10000, "ether")}).buy() 98 | 99 | 100 | def test_set_minimum_funding_goal(crowdsale, team_multisig): 101 | """Reset minimum funding goal 102 | 103 | Spec 3.4. 104 | """ 105 | 106 | # Original goal 107 | # We lose ~1 ETH precision in the conversions 108 | assert abs(in_chf(crowdsale.call().getMinimumFundingGoal()) - 700000) < 10 109 | 110 | # reset it 111 | crowdsale.transact({"from": team_multisig}).setMinimumFundingLimit(8123123 * 10000) 112 | 113 | # New goal 114 | # We lose ~1 ETH precision in the conversions 115 | new_goal_chf = in_chf(crowdsale.call().getMinimumFundingGoal()) 116 | assert abs(new_goal_chf - 8123123) < 10 117 | 118 | 119 | def test_trigger_soft_cap(started_crowdsale, team_multisig, customer, mysterium_pricing): 120 | """See that the soft cap trigger causes the end time rewind. 121 | 122 | Spec 3.5. 123 | """ 124 | 125 | crowdsale = started_crowdsale 126 | 127 | # Let's first change the soft cap as the owner 128 | mysterium_pricing.transact({"from": team_multisig}).setSoftCapCHF(6000000 * 10000) 129 | 130 | old_ends_at = crowdsale.call().endsAt() 131 | 132 | # Some generous person comes and buy all tokens in the soft cap 133 | cap = mysterium_pricing.call().getSoftCapInWeis() 134 | crowdsale.transact({"from": customer, "value": cap+1}).buy() 135 | 136 | # We reached the cap 137 | assert crowdsale.call().isSoftCapReached() 138 | assert crowdsale.call().softCapTriggered() 139 | 140 | new_ends_at = crowdsale.call().endsAt() 141 | 142 | # Now buyers need to hurry 143 | assert new_ends_at < old_ends_at 144 | assert crowdsale.call().getState() == CrowdsaleState.Funding 145 | 146 | # We can still buy after the soft cap is triggered 147 | assert not crowdsale.call().isCrowdsaleFull() 148 | crowdsale.transact({"from": customer, "value": to_wei(1, "ether")}).buy() 149 | 150 | 151 | def test_hard_cao_reached(started_crowdsale, team_multisig, customer, mysterium_pricing): 152 | """Crowdsale is full when the hard cap is reached. 153 | 154 | Spec 3.6. 155 | """ 156 | 157 | crowdsale = started_crowdsale 158 | 159 | # Reset hard cap 160 | crowdsale.transact({"from": team_multisig}).setHardCapCHF(10000000 * 10000) 161 | 162 | hard_cap = crowdsale.call().getHardCap() 163 | 164 | # Some generous person comes and buy all tokens in the world 165 | crowdsale.transact({"from": customer, "value": hard_cap}).buy() 166 | 167 | # We reached the cap 168 | assert crowdsale.call().isCrowdsaleFull() 169 | assert crowdsale.call().getState() == CrowdsaleState.Success 170 | 171 | with pytest.raises(TransactionFailed): 172 | crowdsale.transact({"from": customer, "value": to_wei(1, "ether")}).buy() 173 | 174 | 175 | def test_manual_release(started_crowdsale, mysterium_token, team_multisig): 176 | """Mysterium token transfers can be manually released by team multisig.""" 177 | 178 | assert not mysterium_token.call().released() 179 | mysterium_token.transact({"from": team_multisig}).releaseTokenTransfer() 180 | assert mysterium_token.call().released() 181 | 182 | 183 | def test_distribution_14m(chain, mysterium_token, preico_funding_goal, preico_starts_at, customer, mysterium_finalize_agent, started_crowdsale, team_multisig): 184 | crowdsale = started_crowdsale 185 | 186 | assert crowdsale.call().getState() == CrowdsaleState.Funding 187 | minimum = crowdsale.call().getMinimumFundingGoal() 188 | 189 | assert mysterium_token.call().transferAgents(mysterium_finalize_agent.address) == True 190 | mysterium_finalize_agent.transact({"from": team_multisig}).distribute(14 * 1000000, 204) 191 | 192 | future_round_coins = mysterium_finalize_agent.call().future_round_coins() 193 | foundation_coins = mysterium_finalize_agent.call().foundation_coins() 194 | team_coins = mysterium_finalize_agent.call().team_coins() 195 | seed_coins_vault1 = mysterium_finalize_agent.call().seed_coins_vault1() 196 | seed_coins_vault2 = mysterium_finalize_agent.call().seed_coins_vault2() 197 | 198 | earlybird_coins = 771459337903602 199 | regular_coins = 757142793161612 200 | 201 | foundation_percentage = 9 202 | future_round_percentage = 15 203 | team_percentage = 10 204 | 205 | seed_multiplier = 5 206 | seed_raised_eth = 6000 207 | eth_chf_price = 204 208 | decimal_scale = 10**8 # 8 decimal points, this is here for every function for reference 209 | seed_coins = seed_raised_eth * eth_chf_price * seed_multiplier * decimal_scale 210 | assert seed_coins == 612000000000000 211 | 212 | percentage_of_three = 100 - foundation_percentage - team_percentage - future_round_percentage 213 | earlybird_percentage = earlybird_coins * percentage_of_three / (earlybird_coins+regular_coins+seed_coins) 214 | total_coins = earlybird_coins * 100 / earlybird_percentage 215 | assert total_coins == 3243336562220021 216 | 217 | assert future_round_coins + 3.1 == total_coins * future_round_percentage / 100 218 | assert team_coins + 2.06 == total_coins * team_percentage / 100 219 | assert foundation_coins - 198.1 == total_coins * foundation_percentage / 100 220 | assert seed_coins_vault1 == seed_coins / seed_multiplier 221 | assert seed_coins_vault2 == seed_coins - seed_coins / seed_multiplier 222 | 223 | assert future_round_coins == 486500484333000 224 | assert foundation_coins == 291900290600000 225 | assert team_coins == 324333656222000 226 | assert seed_coins_vault1 == 122400000000000 227 | assert seed_coins_vault2 == 489600000000000 228 | 229 | assert total_coins + 193 == earlybird_coins + regular_coins + future_round_coins + foundation_coins + team_coins + seed_coins_vault1 + seed_coins_vault2 230 | 231 | -------------------------------------------------------------------------------- /tests/test_deploy_all.py: -------------------------------------------------------------------------------- 1 | """Deploy all contracts using the deploy script.""" 2 | import os 3 | 4 | import datetime 5 | import pytest 6 | from eth_utils import to_wei 7 | 8 | from ico.deploy import _deploy_contracts 9 | from ico.definition import load_crowdsale_definitions 10 | from ico.state import CrowdsaleState 11 | from ico.tests.utils import time_travel 12 | 13 | 14 | from web3.contract import Contract 15 | 16 | 17 | @pytest.fixture() 18 | def deploy_address(accounts): 19 | """Operational control account""" 20 | return accounts[9] 21 | 22 | 23 | @pytest.fixture() 24 | def bitcoin_suisse(accounts): 25 | """BitcoinSuisse accounts.""" 26 | return accounts[8] 27 | 28 | 29 | @pytest.fixture() 30 | def fake_seed_investor(accounts): 31 | """Test account planted in fake_seed_investor_data.csv""" 32 | return accounts[7] 33 | 34 | 35 | @pytest.fixture 36 | def everything_deployed(project, chain, web3, accounts, deploy_address): 37 | """Deploy our token plan.""" 38 | yaml_filename = os.path.join(os.path.dirname(__file__), "..", "crowdsales", "mysterium-testrpc.yml") 39 | deployment_name = "kovan" 40 | chain_data = load_crowdsale_definitions(yaml_filename, deployment_name) 41 | runtime_data, statistics, contracts = _deploy_contracts(project, chain, web3, yaml_filename, chain_data, deploy_address) 42 | return contracts 43 | 44 | 45 | @pytest.fixture() 46 | def proxy_buyer_freeze_ends_at(chain, crowdsale) -> Contract: 47 | """When investors can reclaim.""" 48 | return int(datetime.datetime(2017, 6, 10).timestamp()) 49 | 50 | 51 | @pytest.fixture 52 | def proxy_buyer(project, chain, web3, customer, everything_deployed, deploy_address, proxy_buyer_freeze_ends_at): 53 | """Make a preico buy contract and see it gets tokens distributed""" 54 | 55 | # Create finalizer contract 56 | args = [ 57 | deploy_address, 58 | proxy_buyer_freeze_ends_at, 59 | 1, # 1 wei 60 | ] 61 | proxy_buyer, hash = chain.provider.deploy_contract('PreICOProxyBuyer', deploy_args=args) 62 | 63 | # Do a purchase 64 | assert proxy_buyer.call().getState() == 1 65 | proxy_buyer.transact({"value": to_wei(10000, "ether"), "from": customer}).invest() 66 | 67 | # Set ICO 68 | proxy_buyer.transact({"from": deploy_address}).setCrowdsale(everything_deployed["crowdsale"].address) 69 | 70 | return proxy_buyer 71 | 72 | 73 | def test_deploy_all(chain, web3, everything_deployed, proxy_buyer, deploy_address, bitcoin_suisse, customer, customer_2, fake_seed_investor): 74 | """Acceptance test to verify that token sale functions properly.""" 75 | 76 | crowdsale = everything_deployed["crowdsale"] 77 | pricing_strategy = everything_deployed["pricing_strategy"] 78 | token_distribution = everything_deployed["token_distribution"] 79 | intermediate_vault = everything_deployed["intermediate_vault"] 80 | seed_participant_vault = everything_deployed["seed_participant_vault"] 81 | seed_participant_vault_2 = everything_deployed["seed_participant_vault_2"] 82 | founders_vault = everything_deployed["founders_vault"] 83 | future_funding_vault = everything_deployed["future_funding_vault"] 84 | token = everything_deployed["token"] 85 | team_multisig_funds = everything_deployed["team_multisig_funds"] 86 | 87 | assert crowdsale.call().getState() == CrowdsaleState.PreFunding 88 | 89 | # Let's set the daily rate 90 | assert pricing_strategy.call().owner() == deploy_address 91 | assert pricing_strategy.call().crowdsale() == crowdsale.address 92 | pricing_strategy.transact({"from": deploy_address}).setConversionRate(170 * 10000) 93 | 94 | # Bitcoinsuisse shows up 95 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist(bitcoin_suisse, True) 96 | crowdsale.transact({"from": bitcoin_suisse, "value": to_wei(500000 / 170, "ether")}).buy() 97 | assert token.call().balanceOf(bitcoin_suisse) > 0 98 | 99 | # Set up proxy buyer crowdsale addresses 100 | proxy_buyer.transact({"from": deploy_address}).setCrowdsale(crowdsale.address) 101 | 102 | # ICO starts 103 | time_travel(chain, crowdsale.call().startsAt() + 1) 104 | 105 | # Load proxy buyer money 106 | proxy_buyer.transact({"from": deploy_address}).buyForEverybody() 107 | assert token.call().balanceOf(proxy_buyer.address) > 0 108 | 109 | # All current money is in the intermediate vault 110 | assert web3.eth.getBalance(intermediate_vault.address) > 0 111 | 112 | # We are not yet in the soft cap 113 | assert not crowdsale.call().isSoftCapReached() 114 | 115 | one_chf_in_eth = to_wei(1 / 170, "ether") 116 | 117 | before_price = pricing_strategy.call().calculatePrice(one_chf_in_eth, crowdsale.call().weiRaised(), crowdsale.call().tokensSold(), '0x0000000000000000000000000000000000000000', 8) 118 | before_ends_at = crowdsale.call().endsAt() 119 | 120 | # Do a retail transaction that fills soft cap 121 | crowdsale.transact({"from": customer_2, "value": one_chf_in_eth * 6000000}).buy() 122 | assert crowdsale.call().isSoftCapReached() 123 | 124 | after_price = pricing_strategy.call().calculatePrice(one_chf_in_eth, crowdsale.call().weiRaised(), crowdsale.call().tokensSold(), '0x0000000000000000000000000000000000000000', 8) 125 | after_ends_at = crowdsale.call().endsAt() 126 | 127 | assert after_ends_at < before_ends_at # End date got moved 128 | assert after_price < before_price # We get less tokens per ETH 129 | 130 | # Let's close it by reaching end of time 131 | time_travel(chain, crowdsale.call().endsAt() + 1) 132 | 133 | # Check that we have distribution facts correct 134 | chf_raised, chf_rate = token_distribution.call().getDistributionFacts() 135 | assert chf_raised == 8199999 136 | assert chf_rate == 170 137 | 138 | # Finalize the sale 139 | assert crowdsale.call().getState() == CrowdsaleState.Success 140 | assert token_distribution.call().crowdsale() == crowdsale.address 141 | assert token_distribution.call().mysteriumPricing() == pricing_strategy.address 142 | 143 | crowdsale.transact({"from": deploy_address}).finalize() 144 | 145 | # 146 | # Finalize vaults 147 | # 148 | 149 | # Seed vault 1 150 | assert seed_participant_vault.address != seed_participant_vault_2.address # Let's not mix vaults 151 | assert seed_participant_vault.call().getToken() == token.address # Vault 1 is set up 152 | seed_participant_vault.transact({"from": deploy_address}).fetchTokenBalance() 153 | 154 | # Seed vault 2 155 | assert token_distribution.call().seed_coins_vault2() > 0 # We did distribute vault 2 156 | assert seed_participant_vault_2.call().getToken() == token.address # Vault 2 is set up 157 | assert token.call().balanceOf(seed_participant_vault_2.address) > 0 158 | seed_participant_vault_2.transact({"from": deploy_address}).fetchTokenBalance() 159 | 160 | # Future vault 161 | assert token.call().balanceOf(future_funding_vault.address) > 0 162 | future_funding_vault.transact({"from": deploy_address}).fetchTokenBalance() 163 | 164 | # Founders vault 165 | assert token.call().balanceOf(founders_vault.address) > 0 166 | founders_vault.transact({"from": deploy_address}).fetchTokenBalance() 167 | 168 | # 169 | # Long timeline 170 | # 171 | 172 | # Money moves to multisig after two weeks 173 | time_travel(chain, crowdsale.call().endsAt() + 14*24*3600) 174 | money_before = web3.eth.getBalance(team_multisig_funds.address) 175 | intermediate_vault.transact({"from": deploy_address}).unlock() # Move from intermediate to team funds 176 | money_after = web3.eth.getBalance(team_multisig_funds.address) 177 | assert money_after > money_before 178 | 179 | # Token is released after we have money in the multisig 180 | token.transact({"from": deploy_address}).releaseTokenTransfer() 181 | assert token.call().released() # Participants can transfer the token 182 | assert token.call().mintingFinished() # No more tokens can be created 183 | 184 | # Do a test transaction with a single token 185 | token.transact({"from": customer_2}).transfer(customer, 1*10**8) 186 | 187 | # Claim tokens from a preico contract 188 | old_balance = token.call().balanceOf(customer) 189 | proxy_buyer.transact({"from": customer}).claimAll() 190 | new_balance = token.call().balanceOf(customer) 191 | assert new_balance > old_balance 192 | 193 | # After 12 months, claim vaults 194 | time_travel(chain, crowdsale.call().endsAt() + 365 * 24*3600) 195 | 196 | # Claim seed vault 1, get seed investor tokens 197 | seed_participant_vault.transact({"from": fake_seed_investor}).claimAll() 198 | assert token.call().balanceOf(fake_seed_investor) > 0 199 | 200 | # Claim seed vault 2, get even more tokens 201 | old_balance = token.call().balanceOf(fake_seed_investor) 202 | seed_participant_vault_2.transact({"from": fake_seed_investor}).claimAll() 203 | new_balance = token.call().balanceOf(fake_seed_investor) 204 | assert new_balance > old_balance 205 | -------------------------------------------------------------------------------- /tests/test_intermediate_vault.py: -------------------------------------------------------------------------------- 1 | """Intermediate vault.""" 2 | import pytest 3 | import time 4 | 5 | from eth_utils import to_wei 6 | from ethereum.tester import TransactionFailed 7 | from ico.tests.utils import time_travel 8 | 9 | 10 | @pytest.fixture 11 | def vault_opens_at(chain, team_multisig): 12 | return int(time.time() + 30*24*3600) 13 | 14 | 15 | @pytest.fixture 16 | def vault(chain, team_multisig, vault_opens_at): 17 | """Create a vault that locks ETH for 30 days.""" 18 | args = [team_multisig, vault_opens_at] 19 | 20 | tx = { 21 | "from": team_multisig 22 | } 23 | 24 | contract, hash = chain.provider.deploy_contract('IntermediateVault', deploy_args=args, deploy_transaction=tx) 25 | return contract 26 | 27 | 28 | @pytest.fixture 29 | def depositor(accounts): 30 | return accounts[8] 31 | 32 | 33 | def test_unlock_vault(chain, web3, vault, depositor, vault_opens_at, team_multisig): 34 | """Vault unlocks successfully.""" 35 | # Money goes in 36 | test_value = to_wei(1000, "ether") 37 | 38 | vault_balance = web3.eth.getBalance(vault.address) 39 | team_multisig_balance = web3.eth.getBalance(team_multisig) 40 | web3.eth.sendTransaction({"from": depositor, "value": test_value, "to": vault.address}) 41 | 42 | # Vault received ETH 43 | assert web3.eth.getBalance(vault.address) > vault_balance 44 | 45 | # Go to unlock date 46 | time_travel(chain, vault_opens_at+1) 47 | vault.transact({"from": depositor}).unlock() 48 | 49 | # Money was transferred to team multisig 50 | assert web3.eth.getBalance(team_multisig) - team_multisig_balance > test_value - 100000 51 | 52 | # Vault is empty now 53 | assert web3.eth.getBalance(vault.address) == 0 54 | 55 | 56 | def test_unlock_vault_early(chain, web3, vault, depositor, vault_opens_at, team_multisig): 57 | """Vault unlock fails.""" 58 | # Money goes in 59 | test_value = to_wei(1000, "ether") 60 | 61 | vault_balance = web3.eth.getBalance(vault.address) 62 | team_multisig_balance = web3.eth.getBalance(team_multisig) 63 | web3.eth.sendTransaction({"from": depositor, "value": test_value, "to": vault.address}) 64 | 65 | # Vault received ETH 66 | assert web3.eth.getBalance(vault.address) > vault_balance 67 | 68 | # Go to unlock date 69 | time_travel(chain, vault_opens_at-1) 70 | with pytest.raises(TransactionFailed): 71 | vault.transact({"from": depositor}).unlock() 72 | 73 | # Money was transferred to team multisig 74 | assert web3.eth.getBalance(team_multisig) == team_multisig_balance 75 | 76 | # Vault is empty now 77 | assert web3.eth.getBalance(vault.address) == test_value 78 | 79 | 80 | def test_two_deposts(chain, web3, vault, depositor, vault_opens_at, team_multisig): 81 | """Vault accepts several deposits.""" 82 | # Money goes in 83 | test_value = to_wei(1000, "ether") 84 | test_value_2 = to_wei(3333, "ether") 85 | 86 | vault_balance = web3.eth.getBalance(vault.address) 87 | team_multisig_balance = web3.eth.getBalance(team_multisig) 88 | web3.eth.sendTransaction({"from": depositor, "value": test_value, "to": vault.address}) 89 | web3.eth.sendTransaction({"from": depositor, "value": test_value_2, "to": vault.address}) 90 | 91 | # Vault received ETH 92 | assert web3.eth.getBalance(vault.address) > vault_balance 93 | 94 | # Go to unlock date 95 | time_travel(chain, vault_opens_at+1) 96 | vault.transact({"from": depositor}).unlock() 97 | 98 | # Money was transferred to team multisig 99 | assert web3.eth.getBalance(team_multisig) - team_multisig_balance > (test_value + test_value_2) - 100000 100 | 101 | # Vault is empty now 102 | assert web3.eth.getBalance(vault.address) == 0 103 | 104 | 105 | -------------------------------------------------------------------------------- /tests/test_multivault.py: -------------------------------------------------------------------------------- 1 | """Basic token properties""" 2 | 3 | import time 4 | from enum import IntEnum 5 | 6 | import pytest 7 | from ethereum.tester import TransactionFailed 8 | from web3.contract import Contract 9 | 10 | 11 | from ico.tests.utils import time_travel 12 | 13 | 14 | 15 | class MultiVaultState(IntEnum): 16 | Unknown = 0 17 | Holding = 1 18 | Distributing = 2 19 | 20 | 21 | 22 | @pytest.fixture 23 | def mysterium_release_agent(chain, team_multisig, mysterium_mv_token) -> Contract: 24 | """Create a simple release agent (useful for testing).""" 25 | 26 | args = [mysterium_mv_token.address] 27 | 28 | tx = { 29 | "from": team_multisig 30 | } 31 | 32 | contract, hash = chain.provider.deploy_contract('SimpleReleaseAgent', deploy_args=args, deploy_transaction=tx) 33 | return contract 34 | 35 | 36 | @pytest.fixture 37 | def mysterium_mv_token(chain, team_multisig, token_name, token_symbol, initial_supply) -> Contract: 38 | """Create the token contract.""" 39 | 40 | args = ["Mysterium", "MYST", 200, 8] # Owner set 41 | 42 | tx = { 43 | "from": team_multisig 44 | } 45 | 46 | contract, hash = chain.provider.deploy_contract('MysteriumToken', deploy_args=args, deploy_transaction=tx) 47 | 48 | contract.transact({"from": team_multisig}).setReleaseAgent(team_multisig) 49 | contract.transact({"from": team_multisig}).releaseTokenTransfer() 50 | 51 | return contract 52 | 53 | 54 | @pytest.fixture 55 | def freeze_ends_at() -> int: 56 | """Timestamp when the vault unlocks.""" 57 | return int(time.time() + 1000) 58 | 59 | 60 | @pytest.fixture 61 | def mysterium_multivault(chain, mysterium_mv_token, freeze_ends_at, customer, customer_2, team_multisig) -> Contract: 62 | args = [ 63 | team_multisig, 64 | freeze_ends_at 65 | ] 66 | 67 | tx = { 68 | "from": team_multisig 69 | } 70 | 71 | contract, hash = chain.provider.deploy_contract('MultiVault', deploy_args=args, deploy_transaction=tx) 72 | contract.transact({"from": team_multisig}).setToken(mysterium_mv_token.address) 73 | contract.transact({"from": team_multisig}).addInvestor(customer, 30) 74 | contract.transact({"from": team_multisig}).addInvestor(customer_2, 35) 75 | contract.transact({"from": team_multisig}).addInvestor(customer_2, 35) 76 | return contract 77 | 78 | 79 | @pytest.fixture 80 | def mysterium_multivault_zero_days(chain, mysterium_mv_token, freeze_ends_at, customer, customer_2, team_multisig) -> Contract: 81 | """A vault that unlocks immediately.""" 82 | args = [ 83 | team_multisig, 84 | 1 85 | ] 86 | 87 | tx = { 88 | "from": team_multisig 89 | } 90 | 91 | contract, hash = chain.provider.deploy_contract('MultiVault', deploy_args=args, deploy_transaction=tx) 92 | contract.transact({"from": team_multisig}).setToken(mysterium_mv_token.address) 93 | contract.transact({"from": team_multisig}).addInvestor(customer, 30) 94 | contract.transact({"from": team_multisig}).addInvestor(customer_2, 35) 95 | contract.transact({"from": team_multisig}).addInvestor(customer_2, 35) 96 | return contract 97 | 98 | 99 | 100 | def test_multi_vault_initial(mysterium_multivault, customer, customer_2, freeze_ends_at, team_multisig): 101 | """Multi vault holds the initial balances and state.""" 102 | 103 | assert mysterium_multivault.call().balances(customer) == 30 104 | assert mysterium_multivault.call().balances(customer_2) == 70 105 | assert mysterium_multivault.call().getState() == MultiVaultState.Holding 106 | assert mysterium_multivault.call().freezeEndsAt() == freeze_ends_at 107 | assert mysterium_multivault.call().owner() == team_multisig 108 | 109 | 110 | def test_fetch_balance(mysterium_multivault, customer, customer_2, freeze_ends_at, team_multisig, mysterium_mv_token): 111 | """Multi vault gets its token balance..""" 112 | 113 | # Load 200 tokens on the vaule 114 | mysterium_mv_token.transact({"from": team_multisig}).transfer(mysterium_multivault.address, mysterium_mv_token.call().totalSupply()) 115 | 116 | mysterium_multivault.transact({"from": team_multisig}).fetchTokenBalance() 117 | assert mysterium_multivault.call().initialTokenBalance() == 200 118 | 119 | with pytest.raises(TransactionFailed): 120 | # Can be called only once 121 | mysterium_multivault.transact({"from": team_multisig}).fetchTokenBalance() 122 | 123 | 124 | def test_multi_vault_distribute(chain, mysterium_multivault, preico_starts_at, mysterium_mv_token, team_multisig, customer, customer_2, mysterium_release_agent): 125 | """Check that multivaut acts correctly.""" 126 | 127 | assert mysterium_mv_token.call().released() 128 | assert mysterium_mv_token.call().balanceOf(customer) == 0 129 | assert mysterium_mv_token.call().totalSupply() == 200 130 | 131 | # Load all 100% tokens to the vault for the test 132 | mysterium_mv_token.transact({"from": team_multisig}).transfer(mysterium_multivault.address, mysterium_mv_token.call().totalSupply()) 133 | assert mysterium_mv_token.call().balanceOf(mysterium_multivault.address) == mysterium_mv_token.call().totalSupply() 134 | 135 | # Set the distribution balance 136 | mysterium_multivault.transact({"from": team_multisig}).fetchTokenBalance() 137 | 138 | # We pass the vault expiration date 139 | time_travel(chain, mysterium_multivault.call().freezeEndsAt() + 1) 140 | assert mysterium_multivault.call().getState() == MultiVaultState.Distributing 141 | 142 | # Check we calculate claims correctly 143 | assert mysterium_multivault.call().getClaimAmount(customer) + mysterium_multivault.call().getClaimAmount(customer_2) == 200 144 | assert mysterium_multivault.call().getClaimLeft(customer) + mysterium_multivault.call().getClaimLeft(customer_2) == 200 145 | 146 | # First customer gets his tokens 147 | assert mysterium_multivault.call().getClaimAmount(customer) == 60 148 | mysterium_multivault.transact({"from": customer}).claimAll() 149 | assert mysterium_mv_token.call().balanceOf(customer) == 60 # 200*3/10 150 | assert mysterium_multivault.call().getClaimLeft(customer) == 0 151 | 152 | # Then customer 2 claims his tokens in two batches 153 | mysterium_multivault.transact({"from": customer_2}).claim(20) 154 | assert mysterium_mv_token.call().balanceOf(customer_2) == 20 155 | 156 | assert mysterium_multivault.call().getClaimLeft(customer_2) == 120 157 | mysterium_multivault.transact({"from": customer_2}).claim(120) 158 | assert mysterium_mv_token.call().balanceOf(customer_2) == 140 159 | assert mysterium_multivault.call().getClaimLeft(customer_2) == 0 160 | 161 | 162 | def test_multi_vault_claim_too_much(chain, mysterium_multivault, preico_starts_at, mysterium_mv_token, team_multisig, customer, customer_2, mysterium_release_agent): 163 | """Somebody tries to claim too many tokens.""" 164 | 165 | assert mysterium_mv_token.call().released() 166 | assert mysterium_mv_token.call().balanceOf(customer) == 0 167 | assert mysterium_mv_token.call().totalSupply() == 200 168 | 169 | # Load all 100% tokens to the vault for the test 170 | mysterium_mv_token.transact({"from": team_multisig}).transfer(mysterium_multivault.address, mysterium_mv_token.call().totalSupply()) 171 | assert mysterium_mv_token.call().balanceOf(mysterium_multivault.address) == mysterium_mv_token.call().totalSupply() 172 | 173 | # Set the distribution balance 174 | mysterium_multivault.transact({"from": team_multisig}).fetchTokenBalance() 175 | 176 | # We pass the vault expiration date 177 | time_travel(chain, mysterium_multivault.call().freezeEndsAt() + 1) 178 | assert mysterium_multivault.call().getState() == MultiVaultState.Distributing 179 | 180 | # First customer gets his tokens 181 | assert mysterium_multivault.call().getClaimAmount(customer) == 60 182 | with pytest.raises(TransactionFailed): 183 | mysterium_multivault.transact({"from": customer}).claim(61) 184 | 185 | 186 | 187 | def test_multi_vault_claim_early(chain, mysterium_multivault, preico_starts_at, mysterium_mv_token, team_multisig, customer, customer_2, mysterium_release_agent): 188 | """Somebody tries to claim his tokens early.""" 189 | 190 | assert mysterium_mv_token.call().released() 191 | assert mysterium_mv_token.call().balanceOf(customer) == 0 192 | assert mysterium_mv_token.call().totalSupply() == 200 193 | 194 | # Load all 100% tokens to the vault for the test 195 | mysterium_mv_token.transact({"from": team_multisig}).transfer(mysterium_multivault.address, mysterium_mv_token.call().totalSupply()) 196 | assert mysterium_mv_token.call().balanceOf(mysterium_multivault.address) == mysterium_mv_token.call().totalSupply() 197 | 198 | # Set the distribution balance 199 | mysterium_multivault.transact({"from": team_multisig}).fetchTokenBalance() 200 | 201 | # We do not pass the vault expiration date 202 | time_travel(chain, mysterium_multivault.call().freezeEndsAt() - 1) 203 | assert mysterium_multivault.call().getState() == MultiVaultState.Holding 204 | 205 | # We can see the balance even before the transfer kicks in 206 | assert mysterium_multivault.call().getClaimAmount(customer) == 60 207 | 208 | # Early claim request fails 209 | with pytest.raises(TransactionFailed): 210 | mysterium_multivault.transact({"from": customer}).claim(1) 211 | 212 | 213 | def test_multi_vault_distribute_require_fetch(chain, mysterium_multivault_zero_days, preico_starts_at, mysterium_mv_token, team_multisig, customer, customer_2, mysterium_release_agent): 214 | """Zero time vault should not allow access tokens before the owner allows it.""" 215 | 216 | mysterium_multivault = mysterium_multivault_zero_days 217 | 218 | assert mysterium_mv_token.call().released() 219 | assert mysterium_mv_token.call().balanceOf(customer) == 0 220 | assert mysterium_mv_token.call().totalSupply() == 200 221 | 222 | # Load all 100% tokens to the vault for the test 223 | mysterium_mv_token.transact({"from": team_multisig}).transfer(mysterium_multivault.address, mysterium_mv_token.call().totalSupply()) 224 | assert mysterium_mv_token.call().balanceOf(mysterium_multivault.address) == mysterium_mv_token.call().totalSupply() 225 | 226 | # Early claim request fails 227 | with pytest.raises(TransactionFailed): 228 | mysterium_multivault.transact({"from": customer}).claim(1) 229 | 230 | assert mysterium_multivault.call().getState() == MultiVaultState.Holding 231 | 232 | # Set the distribution balance 233 | mysterium_multivault.transact({"from": team_multisig}).fetchTokenBalance() 234 | 235 | assert mysterium_multivault.call().getState() == MultiVaultState.Distributing 236 | 237 | # Check we calculate claims correctly 238 | assert mysterium_multivault.call().getClaimAmount(customer) + mysterium_multivault.call().getClaimAmount(customer_2) == 200 239 | assert mysterium_multivault.call().getClaimLeft(customer) + mysterium_multivault.call().getClaimLeft(customer_2) == 200 240 | 241 | # First customer gets his tokens 242 | assert mysterium_multivault.call().getClaimAmount(customer) == 60 243 | mysterium_multivault.transact({"from": customer}).claimAll() 244 | assert mysterium_mv_token.call().balanceOf(customer) == 60 # 200*3/10 245 | assert mysterium_multivault.call().getClaimLeft(customer) == 0 246 | 247 | # Then customer 2 claims his tokens in two batches 248 | mysterium_multivault.transact({"from": customer_2}).claim(20) 249 | assert mysterium_mv_token.call().balanceOf(customer_2) == 20 250 | 251 | assert mysterium_multivault.call().getClaimLeft(customer_2) == 120 252 | mysterium_multivault.transact({"from": customer_2}).claim(120) 253 | assert mysterium_mv_token.call().balanceOf(customer_2) == 140 254 | assert mysterium_multivault.call().getClaimLeft(customer_2) == 0 255 | -------------------------------------------------------------------------------- /tests/test_mysterium_token.py: -------------------------------------------------------------------------------- 1 | """Basic token properties""" 2 | 3 | import datetime 4 | 5 | import pytest 6 | from decimal import Decimal 7 | from eth_utils import to_wei 8 | from ethereum.tester import TransactionFailed 9 | from web3.contract import Contract 10 | 11 | 12 | from ico.tests.utils import time_travel 13 | from ico.state import CrowdsaleState 14 | from ico.utils import decimalize_token_amount 15 | 16 | @pytest.fixture 17 | def token_new_name() -> str: 18 | return "New name" 19 | 20 | @pytest.fixture 21 | def token_new_symbol() -> str: 22 | return "NEW" 23 | 24 | @pytest.fixture 25 | def mysterium_token(chain, team_multisig, token_name, token_symbol, initial_supply) -> Contract: 26 | """Create the token contract.""" 27 | 28 | args = ["Mysterium", "MYST", 0, 8] # Owner set 29 | 30 | tx = { 31 | "from": team_multisig 32 | } 33 | 34 | contract, hash = chain.provider.deploy_contract('CrowdsaleToken', deploy_args=args, deploy_transaction=tx) 35 | return contract 36 | 37 | 38 | def test_token_interface(mysterium_token, team_multisig, token_new_name, token_new_symbol): 39 | """Deployed token properties are correct.""" 40 | 41 | assert mysterium_token.call().totalSupply() == 0 42 | assert mysterium_token.call().symbol() == "MYST" 43 | assert mysterium_token.call().name() == "Mysterium" 44 | assert mysterium_token.call().decimals() == 8 45 | assert mysterium_token.call().owner() == team_multisig 46 | assert mysterium_token.call().upgradeMaster() == team_multisig 47 | 48 | mysterium_token.transact({"from": team_multisig}).setTokenInformation(token_new_name, token_new_symbol) 49 | assert mysterium_token.call().name() == token_new_name 50 | assert mysterium_token.call().symbol() == token_new_symbol 51 | -------------------------------------------------------------------------------- /tests/test_set_chf.py: -------------------------------------------------------------------------------- 1 | """Basic token properties""" 2 | 3 | import datetime 4 | 5 | import pytest 6 | from decimal import Decimal 7 | from eth_utils import to_wei 8 | from ethereum.tester import TransactionFailed 9 | from web3.contract import Contract 10 | 11 | 12 | from ico.tests.utils import time_travel 13 | from ico.state import CrowdsaleState 14 | from ico.utils import decimalize_token_amount 15 | 16 | 17 | 18 | @pytest.fixture 19 | def mysterium_pricing(chain, preico_token_price, team_multisig) -> Contract: 20 | """Flat pricing contact.""" 21 | args = [ 22 | 120 * 10000, 23 | ] 24 | tx = { 25 | "from": team_multisig, 26 | } 27 | pricing_strategy, hash = chain.provider.deploy_contract('MysteriumPricing', deploy_args=args, deploy_transaction=tx) 28 | return pricing_strategy 29 | 30 | 31 | def test_convert(mysterium_pricing): 32 | assert mysterium_pricing.call().convertToWei(120*10000) == 1 * 10**18 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | from populus.chain import TestRPCChain 2 | from web3.testing import Testing 3 | 4 | 5 | def time_travel(chain: TestRPCChain, timestamp: float): 6 | """Travel to a certain block in the future in chain.""" 7 | web3 = chain.web3 8 | testing = Testing(web3) 9 | testing.timeTravel(timestamp) 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35 3 | 4 | [testenv:flake8] 5 | basepython=python 6 | deps=flake8 7 | commands=flake8 ico 8 | 9 | [testenv] 10 | setenv = 11 | PYTHONPATH = {toxinidir}:{toxinidir}/ico 12 | deps = 13 | -r{toxinidir}/requirements.txt 14 | commands = 15 | pip install -U pip 16 | py.test --basetemp={envtmpdir} ico/tests 17 | 18 | usedevelop = true 19 | --------------------------------------------------------------------------------