├── .editorconfig ├── .gitignore ├── .gitmodules ├── .travis.yml ├── HISTORY.rst ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── README.rst ├── contracts ├── AMLToken.sol ├── AllocatedCrowdsale.sol ├── AllocatedCrowdsaleMixin.sol ├── BitrewardsToken.sol ├── BonusFinalizeAgent.sol ├── BurnableCrowdsaleToken.sol ├── BurnableToken.sol ├── BytesDeserializer.sol ├── CentrallyIssuedToken.sol ├── Crowdsale.sol ├── CrowdsaleBase.sol ├── CrowdsaleToken.sol ├── DefaultFinalizeAgent.sol ├── EthTranchePricing.sol ├── ExtraFinalizeAgent.sol ├── FinalizeAgent.sol ├── FlatPricing.sol ├── FractionalERC20.sol ├── GnosisWallet.sol ├── Haltable.sol ├── Issuer.sol ├── KYCCrowdsale.sol ├── KYCPayloadDeserializer.sol ├── MilestonePricing.sol ├── MintableToken.sol ├── MintedEthCappedCrowdsale.sol ├── MintedTokenCappedCrowdsale.sol ├── NullFinalizeAgent.sol ├── PaymentForwarder.sol ├── PreICOProxyBuyer.sol ├── PresaleFundCollector.sol ├── PricingStrategy.sol ├── Recoverable.sol ├── RelaunchedCrowdsale.sol ├── ReleasableToken.sol ├── SafeMathLib.sol ├── StandardTokenExt.sol ├── TimeVault.sol ├── TokenTranchePricing.sol ├── TokenVault.sol ├── UncappedCrowdsale.sol ├── UpgradeAgent.sol └── UpgradeableToken.sol ├── crowdsales └── bitrewards-ico.yml ├── docs ├── Makefile └── source │ ├── _templates │ └── layout.html │ ├── chain.rst │ ├── commands.rst │ ├── conf.py │ ├── contracts.rst │ ├── designchoices.rst │ ├── index.rst │ ├── install.rst │ ├── interact.rst │ ├── intro.rst │ ├── other.rst │ ├── screenshots │ ├── etherscan_token_transfer.png │ ├── etherscan_verify.png │ ├── ipython.png │ ├── myetherwallet_token.png │ ├── presale_invest.png │ └── walkthrough.png │ ├── support.rst │ ├── test.rst │ └── verification.rst ├── ico ├── cmd │ ├── combine.py │ ├── deploycontracts.py │ ├── deploytoken.py │ ├── distributetokens.py │ ├── investors.py │ ├── rawinvestments.py │ ├── rebuildcrowdsale.py │ ├── refund.py │ └── tokenvault.py ├── definition.py ├── deploy.py ├── earlypresale.py ├── etherscan.py ├── importexpand.py ├── kyc.py ├── sign.py ├── state.py ├── tests │ ├── conftest.py │ ├── contracts │ │ ├── test_amltoken.py │ │ ├── test_approval.py │ │ ├── test_burn.py │ │ ├── test_bytes_deserializing.py │ │ ├── test_deploy_acceptance.py │ │ ├── test_erc20.py │ │ ├── test_eth_capped.py │ │ ├── test_eth_tranche_pricing.py │ │ ├── test_finalize.py │ │ ├── test_forwarder.py │ │ ├── test_issuer.py │ │ ├── test_kyc_payload_deserializing.py │ │ ├── test_kyccrowdsale.py │ │ ├── test_milestone_pricing.py │ │ ├── test_preallocate.py │ │ ├── test_preico_proxy_buy.py │ │ ├── test_presale.py │ │ ├── test_refund.py │ │ ├── test_relaunch_with_new_token.py │ │ ├── test_relaunch_with_old_token.py │ │ ├── test_releasable.py │ │ ├── test_require_customer_id.py │ │ ├── test_require_signed_address.py │ │ ├── test_time_vault.py │ │ ├── test_token.py │ │ ├── test_token_capped.py │ │ ├── test_token_tranche_pricing.py │ │ ├── test_token_vault.py │ │ ├── test_uncapped_flatprice.py │ │ └── test_upgrade.py │ ├── fixtures │ │ ├── amltoken.py │ │ ├── finalize.py │ │ ├── flatprice.py │ │ ├── general.py │ │ ├── presale.py │ │ └── releasable.py │ ├── tools │ │ ├── conftest.py │ │ ├── manual_etherscan.py │ │ ├── test_crowdsale_deployer.py │ │ ├── test_expand.py │ │ ├── test_libraries.py │ │ └── test_load_yaml.py │ └── utils.py └── utils.py ├── install_solc.sh ├── populus.json ├── requirements.txt ├── setup.cfg ├── setup.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 | 31 | [populus.json] 32 | indent_size = 2 33 | -------------------------------------------------------------------------------- /.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 | .vscode 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | # pyenv python configuration file 64 | .python-version 65 | 66 | chains/* 67 | eireg/chains/* 68 | venv 69 | build 70 | 71 | # Where Populus has deployed contracts 72 | registrar.json 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | *.ipynb 77 | 78 | # Crowdsale definitions 79 | *.csv 80 | 81 | # Customer test cases 82 | test_client*.py 83 | 84 | block-timestamps.json 85 | 86 | # JSON state files for refund script 87 | refund-state* 88 | 89 | # A folder for test CSV files 90 | csvs 91 | 92 | # Libreoffice tmp files 93 | .~* 94 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zeppelin"] 2 | path = zeppelin 3 | url = https://github.com/OpenZeppelin/zeppelin-solidity.git 4 | -------------------------------------------------------------------------------- /.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.18 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 | notifications: 35 | webhooks: 36 | urls: 37 | - https://webhooks.gitter.im/e/076ad81483d6aad0f72a 38 | on_success: change 39 | on_failure: always 40 | on_start: never 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 6 | 0.1 (unreleased) 7 | ---------------- 8 | 9 | * First release on PyPI. 10 | 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 TokenMarket Ltd., Gibraltar, https://tokenmarket.net 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IcoSmartContract 2 | Smart Contract of BitRewards ICO (WIP) 3 | -------------------------------------------------------------------------------- /contracts/AMLToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.14; 8 | 9 | import "./BurnableCrowdsaleToken.sol"; 10 | 11 | 12 | /** 13 | * The AML Token 14 | * 15 | * This subset of BurnableCrowdsaleToken gives the Owner a possibility to 16 | * reclaim tokens from a participant before the token is released 17 | * after a participant has failed a prolonged AML process. 18 | * 19 | * It is assumed that the anti-money laundering process depends on blockchain data. 20 | * The data is not available before the transaction and not for the smart contract. 21 | * Thus, we need to implement logic to handle AML failure cases post payment. 22 | * We give a time window before the token release for the token sale owners to 23 | * complete the AML and claw back all token transactions that were 24 | * caused by rejected purchases. 25 | */ 26 | contract AMLToken is BurnableCrowdsaleToken { 27 | 28 | // An event when the owner has reclaimed non-released tokens 29 | event OwnerReclaim(address fromWhom, uint amount); 30 | 31 | function AMLToken(string _name, string _symbol, uint _initialSupply, uint _decimals, bool _mintable) BurnableCrowdsaleToken(_name, _symbol, _initialSupply, _decimals, _mintable) { 32 | 33 | } 34 | 35 | /// @dev Here the owner can reclaim the tokens from a participant if 36 | /// the token is not released yet. Refund will be handled offband. 37 | /// @param fromWhom address of the participant whose tokens we want to claim 38 | function transferToOwner(address fromWhom) onlyOwner { 39 | if (released) revert(); 40 | 41 | uint amount = balanceOf(fromWhom); 42 | balances[fromWhom] = balances[fromWhom].sub(amount); 43 | balances[owner] = balances[owner].add(amount); 44 | Transfer(fromWhom, owner, amount); 45 | OwnerReclaim(fromWhom, amount); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/AllocatedCrowdsale.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./AllocatedCrowdsaleMixin.sol"; 10 | import "./Crowdsale.sol"; 11 | 12 | 13 | /** 14 | * An implementation of allocated crowdsale. 15 | * 16 | * This implementation does not have KYC logic (vs. KYCCrowdsale). 17 | * 18 | */ 19 | contract AllocatedCrowdsale is AllocatedCrowdsaleMixin, Crowdsale { 20 | 21 | function AllocatedCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal, address _beneficiary) Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) AllocatedCrowdsaleMixin(_beneficiary) { 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /contracts/AllocatedCrowdsaleMixin.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./CrowdsaleBase.sol"; 10 | 11 | /** 12 | * A mixin that is selling tokens from a preallocated pool 13 | * 14 | * - Tokens have precreated supply "premined" 15 | * 16 | * - Token owner must transfer sellable tokens to the crowdsale contract using ERC20.approve() 17 | * 18 | * - The mixin does not implement buy entry point. 19 | * 20 | */ 21 | contract AllocatedCrowdsaleMixin is CrowdsaleBase { 22 | 23 | /* The party who holds the full token pool and has approve()'ed tokens for this crowdsale */ 24 | address public beneficiary; 25 | 26 | /** 27 | * @param _beneficiary The account who has performed approve() to allocate tokens for the token sale. 28 | * 29 | */ 30 | function AllocatedCrowdsaleMixin(address _beneficiary) { 31 | beneficiary = _beneficiary; 32 | } 33 | 34 | /** 35 | * Called from invest() to confirm if the curret investment does not break our cap rule. 36 | */ 37 | function isBreakingCap(uint weiAmount, uint tokenAmount, uint weiRaisedTotal, uint tokensSoldTotal) constant returns (bool limitBroken) { 38 | if(tokenAmount > getTokensLeft()) { 39 | return true; 40 | } else { 41 | return false; 42 | } 43 | } 44 | 45 | /** 46 | * We are sold out when our approve pool becomes empty. 47 | */ 48 | function isCrowdsaleFull() public constant returns (bool) { 49 | return getTokensLeft() == 0; 50 | } 51 | 52 | /** 53 | * Get the amount of unsold tokens allocated to this contract; 54 | */ 55 | function getTokensLeft() public constant returns (uint) { 56 | return token.allowance(owner, this); 57 | } 58 | 59 | /** 60 | * Transfer tokens from approve() pool to the buyer. 61 | * 62 | * Use approve() given to this crowdsale to distribute the tokens. 63 | */ 64 | function assignTokens(address receiver, uint tokenAmount) internal { 65 | if(!token.transferFrom(beneficiary, receiver, tokenAmount)) throw; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/BitrewardsToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | import "./CrowdsaleToken.sol"; 4 | import "./ReleasableToken.sol"; 5 | 6 | contract BitrewardsToken is CrowdsaleToken, ReleasableToken { 7 | 8 | } -------------------------------------------------------------------------------- /contracts/BonusFinalizeAgent.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | import "./Crowdsale.sol"; 10 | import "./CrowdsaleToken.sol"; 11 | import "./SafeMathLib.sol"; 12 | 13 | /** 14 | * At the end of the successful crowdsale allocate % bonus of tokens to the team. 15 | * 16 | * Unlock tokens. 17 | * 18 | * BonusAllocationFinal must be set as the minting agent for the MintableToken. 19 | * 20 | */ 21 | contract BonusFinalizeAgent is FinalizeAgent { 22 | 23 | using SafeMathLib for uint; 24 | 25 | CrowdsaleToken public token; 26 | Crowdsale public crowdsale; 27 | 28 | /** Total percent of tokens minted to the team at the end of the sale as base points (0.0001) */ 29 | uint public bonusBasePoints; 30 | 31 | /** Where we move the tokens at the end of the sale. */ 32 | address public teamMultisig; 33 | 34 | /* How much bonus tokens we allocated */ 35 | uint public allocatedBonus; 36 | 37 | function BonusFinalizeAgent(CrowdsaleToken _token, Crowdsale _crowdsale, uint _bonusBasePoints, address _teamMultisig) { 38 | token = _token; 39 | crowdsale = _crowdsale; 40 | if(address(crowdsale) == 0) { 41 | throw; 42 | } 43 | 44 | teamMultisig = _teamMultisig; 45 | if(address(teamMultisig) == 0) { 46 | throw; 47 | } 48 | 49 | bonusBasePoints = _bonusBasePoints; 50 | } 51 | 52 | /* Can we run finalize properly */ 53 | function isSane() public constant returns (bool) { 54 | return (token.mintAgents(address(this)) == true) && (token.releaseAgent() == address(this)); 55 | } 56 | 57 | /** Called once by crowdsale finalize() if the sale was success. */ 58 | function finalizeCrowdsale() { 59 | if(msg.sender != address(crowdsale)) { 60 | throw; 61 | } 62 | 63 | // How many % of tokens the founders and others get 64 | uint tokensSold = crowdsale.tokensSold(); 65 | allocatedBonus = tokensSold.times(bonusBasePoints) / 10000; 66 | 67 | // move tokens to the team multisig wallet 68 | token.mint(teamMultisig, allocatedBonus); 69 | 70 | // Make token transferable 71 | token.releaseTokenTransfer(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /contracts/BurnableCrowdsaleToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./BurnableToken.sol"; 10 | import "./CrowdsaleToken.sol"; 11 | 12 | /** 13 | * A crowdsaled token that you can also burn. 14 | * 15 | */ 16 | contract BurnableCrowdsaleToken is BurnableToken, CrowdsaleToken { 17 | 18 | function BurnableCrowdsaleToken(string _name, string _symbol, uint _initialSupply, uint _decimals, bool _mintable) 19 | CrowdsaleToken(_name, _symbol, _initialSupply, _decimals, _mintable) { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/BurnableToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./StandardTokenExt.sol"; 10 | 11 | contract BurnableToken is StandardTokenExt { 12 | 13 | // @notice An address for the transfer event where the burned tokens are transferred in a faux Transfer event 14 | address public constant BURN_ADDRESS = 0; 15 | 16 | /** How many tokens we burned */ 17 | event Burned(address burner, uint burnedAmount); 18 | 19 | /** 20 | * Burn extra tokens from a balance. 21 | * 22 | */ 23 | function burn(uint burnAmount) { 24 | address burner = msg.sender; 25 | balances[burner] = balances[burner].sub(burnAmount); 26 | totalSupply = totalSupply.sub(burnAmount); 27 | Burned(burner, burnAmount); 28 | 29 | // Inform the blockchain explores that track the 30 | // balances only by a transfer event that the balance in this 31 | // address has decreased 32 | Transfer(burner, BURN_ADDRESS, burnAmount); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/BytesDeserializer.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | /** 8 | * Deserialize bytes payloads. 9 | * 10 | * Values are in big-endian byte order. 11 | * 12 | */ 13 | library BytesDeserializer { 14 | 15 | /** 16 | * Extract 256-bit worth of data from the bytes stream. 17 | */ 18 | function slice32(bytes b, uint offset) constant returns (bytes32) { 19 | bytes32 out; 20 | 21 | for (uint i = 0; i < 32; i++) { 22 | out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); 23 | } 24 | return out; 25 | } 26 | 27 | /** 28 | * Extract Ethereum address worth of data from the bytes stream. 29 | */ 30 | function sliceAddress(bytes b, uint offset) constant returns (address) { 31 | bytes32 out; 32 | 33 | for (uint i = 0; i < 20; i++) { 34 | out |= bytes32(b[offset + i] & 0xFF) >> ((i+12) * 8); 35 | } 36 | return address(uint(out)); 37 | } 38 | 39 | /** 40 | * Extract 128-bit worth of data from the bytes stream. 41 | */ 42 | function slice16(bytes b, uint offset) constant returns (bytes16) { 43 | bytes16 out; 44 | 45 | for (uint i = 0; i < 16; i++) { 46 | out |= bytes16(b[offset + i] & 0xFF) >> (i * 8); 47 | } 48 | return out; 49 | } 50 | 51 | /** 52 | * Extract 32-bit worth of data from the bytes stream. 53 | */ 54 | function slice4(bytes b, uint offset) constant returns (bytes4) { 55 | bytes4 out; 56 | 57 | for (uint i = 0; i < 4; i++) { 58 | out |= bytes4(b[offset + i] & 0xFF) >> (i * 8); 59 | } 60 | return out; 61 | } 62 | 63 | /** 64 | * Extract 16-bit worth of data from the bytes stream. 65 | */ 66 | function slice2(bytes b, uint offset) constant returns (bytes2) { 67 | bytes2 out; 68 | 69 | for (uint i = 0; i < 2; i++) { 70 | out |= bytes2(b[offset + i] & 0xFF) >> (i * 8); 71 | } 72 | return out; 73 | } 74 | 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /contracts/CentrallyIssuedToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | import "./BurnableToken.sol"; 4 | import "./UpgradeableToken.sol"; 5 | 6 | 7 | /** 8 | * Centrally issued Ethereum token. 9 | * 10 | * We mix in burnable and upgradeable traits. 11 | * 12 | * Token supply is created in the token contract creation and allocated to owner. 13 | * The owner can then transfer from its supply to crowdsale participants. 14 | * The owner, or anybody, can burn any excessive tokens they are holding. 15 | * 16 | */ 17 | contract CentrallyIssuedToken is BurnableToken, UpgradeableToken { 18 | 19 | // Token meta information 20 | string public name; 21 | string public symbol; 22 | uint public decimals; 23 | 24 | // Token release switch 25 | bool public released = false; 26 | 27 | // The date before the release must be finalized or upgrade path will be forced 28 | uint public releaseFinalizationDate; 29 | 30 | /** Name and symbol were updated. */ 31 | event UpdatedTokenInformation(string newName, string newSymbol); 32 | 33 | function CentrallyIssuedToken(address _owner, string _name, string _symbol, uint _totalSupply, uint _decimals, uint _releaseFinalizationDate) UpgradeableToken(_owner) { 34 | name = _name; 35 | symbol = _symbol; 36 | totalSupply = _totalSupply; 37 | decimals = _decimals; 38 | 39 | // Allocate initial balance to the owner 40 | balances[_owner] = _totalSupply; 41 | 42 | releaseFinalizationDate = _releaseFinalizationDate; 43 | } 44 | 45 | /** 46 | * Owner can update token information here. 47 | * 48 | * It is often useful to conceal the actual token association, until 49 | * the token operations, like central issuance or reissuance have been completed. 50 | * In this case the initial token can be supplied with empty name and symbol information. 51 | * 52 | * This function allows the token owner to rename the token after the operations 53 | * have been completed and then point the audience to use the token contract. 54 | */ 55 | function setTokenInformation(string _name, string _symbol) { 56 | 57 | if(msg.sender != upgradeMaster) { 58 | throw; 59 | } 60 | 61 | if(bytes(name).length > 0 || bytes(symbol).length > 0) { 62 | // Information already set 63 | // Allow owner to set this information only once 64 | throw; 65 | } 66 | 67 | name = _name; 68 | symbol = _symbol; 69 | UpdatedTokenInformation(name, symbol); 70 | } 71 | 72 | 73 | /** 74 | * Kill switch for the token in the case of distribution issue. 75 | * 76 | */ 77 | function transfer(address _to, uint _value) returns (bool success) { 78 | 79 | if(now > releaseFinalizationDate) { 80 | if(!released) { 81 | throw; 82 | } 83 | } 84 | 85 | return super.transfer(_to, _value); 86 | } 87 | 88 | /** 89 | * One way function to perform the final token release. 90 | */ 91 | function releaseTokenTransfer() { 92 | if(msg.sender != upgradeMaster) { 93 | throw; 94 | } 95 | 96 | released = true; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/CrowdsaleToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./UpgradeableToken.sol"; 10 | import "./ReleasableToken.sol"; 11 | import "./MintableToken.sol"; 12 | 13 | 14 | /** 15 | * A crowdsaled token. 16 | * 17 | * An ERC-20 token designed specifically for crowdsales with investor protection and further development path. 18 | * 19 | * - The token transfer() is disabled until the crowdsale is over 20 | * - The token contract gives an opt-in upgrade path to a new contract 21 | * - The same token can be part of several crowdsales through approve() mechanism 22 | * - The token can be capped (supply set in the constructor) or uncapped (crowdsale contract can mint new tokens) 23 | * 24 | */ 25 | contract CrowdsaleToken is ReleasableToken, MintableToken, UpgradeableToken { 26 | 27 | /** Name and symbol were updated. */ 28 | event UpdatedTokenInformation(string newName, string newSymbol); 29 | 30 | string public name; 31 | 32 | string public symbol; 33 | 34 | uint public decimals; 35 | 36 | /** 37 | * Construct the token. 38 | * 39 | * This token must be created through a team multisig wallet, so that it is owned by that wallet. 40 | * 41 | * @param _name Token name 42 | * @param _symbol Token symbol - should be all caps 43 | * @param _initialSupply How many tokens we start with 44 | * @param _decimals Number of decimal places 45 | * @param _mintable Are new tokens created over the crowdsale or do we distribute only the initial supply? Note that when the token becomes transferable the minting always ends. 46 | */ 47 | function CrowdsaleToken(string _name, string _symbol, uint _initialSupply, uint _decimals, bool _mintable) 48 | UpgradeableToken(msg.sender) { 49 | 50 | // Create any address, can be transferred 51 | // to team multisig via changeOwner(), 52 | // also remember to call setUpgradeMaster() 53 | owner = msg.sender; 54 | 55 | name = _name; 56 | symbol = _symbol; 57 | 58 | totalSupply = _initialSupply; 59 | 60 | decimals = _decimals; 61 | 62 | // Create initially all balance on the team multisig 63 | balances[owner] = totalSupply; 64 | 65 | if(totalSupply > 0) { 66 | Minted(owner, totalSupply); 67 | } 68 | 69 | // No more new supply allowed after the token creation 70 | if(!_mintable) { 71 | mintingFinished = true; 72 | if(totalSupply == 0) { 73 | throw; // Cannot create a token without supply and no minting 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * When token is released to be transferable, enforce no new tokens can be created. 80 | */ 81 | function releaseTokenTransfer() public onlyReleaseAgent { 82 | mintingFinished = true; 83 | super.releaseTokenTransfer(); 84 | } 85 | 86 | /** 87 | * Allow upgrade agent functionality kick in only if the crowdsale was success. 88 | */ 89 | function canUpgrade() public constant returns(bool) { 90 | return released && super.canUpgrade(); 91 | } 92 | 93 | /** 94 | * Owner can update token information here. 95 | * 96 | * It is often useful to conceal the actual token association, until 97 | * the token operations, like central issuance or reissuance have been completed. 98 | * 99 | * This function allows the token owner to rename the token after the operations 100 | * have been completed and then point the audience to use the token contract. 101 | */ 102 | function setTokenInformation(string _name, string _symbol) onlyOwner { 103 | name = _name; 104 | symbol = _symbol; 105 | 106 | UpdatedTokenInformation(name, symbol); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /contracts/DefaultFinalizeAgent.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | import "./Crowdsale.sol"; 10 | import "./ReleasableToken.sol"; 11 | 12 | /** 13 | * The default behavior for the crowdsale end. 14 | * 15 | * Unlock tokens. 16 | */ 17 | contract DefaultFinalizeAgent is FinalizeAgent { 18 | 19 | ReleasableToken public token; 20 | Crowdsale public crowdsale; 21 | 22 | function DefaultFinalizeAgent(ReleasableToken _token, Crowdsale _crowdsale) { 23 | token = _token; 24 | crowdsale = _crowdsale; 25 | } 26 | 27 | /** Check that we can release the token */ 28 | function isSane() public constant returns (bool) { 29 | return (token.releaseAgent() == address(this)); 30 | } 31 | 32 | /** Called once by crowdsale finalize() if the sale was success. */ 33 | function finalizeCrowdsale() public { 34 | if(msg.sender != address(crowdsale)) { 35 | throw; 36 | } 37 | token.releaseTokenTransfer(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /contracts/ExtraFinalizeAgent.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | import "./Crowdsale.sol"; 10 | import "./CrowdsaleToken.sol"; 11 | import "./SafeMathLib.sol"; 12 | 13 | /** 14 | * At the end of the successful crowdsale allocate % bonus of tokens to the team. 15 | * 16 | * Do not unlock the tokens. 17 | * 18 | * BonusAllocationFinal must be set as the minting agent for the MintableToken. 19 | * 20 | */ 21 | contract ExtraFinalizeAgent is FinalizeAgent { 22 | 23 | using SafeMathLib for uint; 24 | 25 | CrowdsaleToken public token; 26 | Crowdsale public crowdsale; 27 | 28 | /** Total percent of tokens minted to the team at the end of the sale as base points (0.0001) */ 29 | uint public bonusBasePoints; 30 | 31 | /** Where we move the tokens at the end of the sale. */ 32 | address public teamMultisig; 33 | 34 | /* How much bonus tokens we allocated */ 35 | uint public allocatedBonus; 36 | 37 | /* How many tokens other finalizers will allocate and we do not count these in */ 38 | uint public accountedTokenSales; 39 | 40 | function ExtraFinalizeAgent(CrowdsaleToken _token, Crowdsale _crowdsale, uint _bonusBasePoints, address _teamMultisig, uint _accountedTokenSales) { 41 | token = _token; 42 | crowdsale = _crowdsale; 43 | 44 | if(address(crowdsale) == 0) { 45 | throw; 46 | } 47 | 48 | teamMultisig = _teamMultisig; 49 | if(address(teamMultisig) == 0) { 50 | throw; 51 | } 52 | 53 | accountedTokenSales = _accountedTokenSales; 54 | } 55 | 56 | /* Can we run finalize properly */ 57 | function isSane() public constant returns (bool) { 58 | return (token.mintAgents(address(this)) == true); 59 | } 60 | 61 | /** Called once by crowdsale finalize() if the sale was success. */ 62 | function finalizeCrowdsale() { 63 | if(msg.sender != address(crowdsale)) { 64 | throw; 65 | } 66 | 67 | // How many % of tokens the founders and others get 68 | uint tokensSold = crowdsale.tokensSold().minus(accountedTokenSales); 69 | allocatedBonus = tokensSold.times(bonusBasePoints) / 10000; 70 | 71 | // move tokens to the team multisig wallet 72 | token.mint(teamMultisig, allocatedBonus); 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /contracts/FinalizeAgent.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | /** 10 | * Finalize agent defines what happens at the end of succeseful crowdsale. 11 | * 12 | * - Allocate tokens for founders, bounties and community 13 | * - Make tokens transferable 14 | * - etc. 15 | */ 16 | contract FinalizeAgent { 17 | 18 | function isFinalizeAgent() public constant returns(bool) { 19 | return true; 20 | } 21 | 22 | /** Return true if we can run finalizeCrowdsale() properly. 23 | * 24 | * This is a safety check function that doesn't allow crowdsale to begin 25 | * unless the finalizer has been set up properly. 26 | */ 27 | function isSane() public constant returns (bool); 28 | 29 | /** Called once by crowdsale finalize() if the sale was success. */ 30 | function finalizeCrowdsale(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /contracts/FlatPricing.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.12; 8 | 9 | import "./PricingStrategy.sol"; 10 | import "./SafeMathLib.sol"; 11 | 12 | /** 13 | * Fixed crowdsale pricing - everybody gets the same price. 14 | */ 15 | contract FlatPricing is PricingStrategy { 16 | 17 | using SafeMathLib for uint; 18 | 19 | /* How many weis one token costs */ 20 | uint public oneTokenInWei; 21 | 22 | function FlatPricing(uint _oneTokenInWei) { 23 | require(_oneTokenInWei > 0); 24 | oneTokenInWei = _oneTokenInWei; 25 | } 26 | 27 | /** 28 | * Calculate the current price for buy in amount. 29 | * 30 | */ 31 | function calculatePrice(uint value, uint weiRaised, uint tokensSold, address msgSender, uint decimals) public constant returns (uint) { 32 | uint multiplier = 10 ** decimals; 33 | return value.times(multiplier) / oneTokenInWei; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /contracts/FractionalERC20.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "zeppelin/contracts/token/ERC20.sol"; 10 | 11 | /** 12 | * A token that defines fractional units as decimals. 13 | */ 14 | contract FractionalERC20 is ERC20 { 15 | 16 | uint public decimals; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /contracts/Haltable.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | import "zeppelin/contracts/ownership/Ownable.sol"; 10 | 11 | /* 12 | * Haltable 13 | * 14 | * Abstract contract that allows children to implement an 15 | * emergency stop mechanism. Differs from Pausable by causing a throw when in halt mode. 16 | * 17 | * 18 | * Originally envisioned in FirstBlood ICO contract. 19 | */ 20 | contract Haltable is Ownable { 21 | bool public halted; 22 | 23 | modifier stopInEmergency { 24 | if (halted) throw; 25 | _; 26 | } 27 | 28 | modifier stopNonOwnersInEmergency { 29 | if (halted && msg.sender != owner) throw; 30 | _; 31 | } 32 | 33 | modifier onlyInEmergency { 34 | if (!halted) throw; 35 | _; 36 | } 37 | 38 | // called by the owner on emergency, triggers stopped state 39 | function halt() external onlyOwner { 40 | halted = true; 41 | } 42 | 43 | // called by the owner on end of emergency, returns to normal state 44 | function unhalt() external onlyOwner onlyInEmergency { 45 | halted = false; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /contracts/Issuer.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./StandardTokenExt.sol"; 10 | import "zeppelin/contracts/ownership/Ownable.sol"; 11 | 12 | /** 13 | * Issuer manages token distribution after the crowdsale. 14 | * 15 | * This contract is fed a CSV file with Ethereum addresses and their 16 | * issued token balances. 17 | * 18 | * Issuer act as a gate keeper to ensure there is no double issuance 19 | * per address, in the case we need to do several issuance batches, 20 | * there is a race condition or there is a fat finger error. 21 | * 22 | * Issuer contract gets allowance from the team multisig to distribute tokens. 23 | * 24 | */ 25 | contract Issuer is Ownable { 26 | 27 | /** Map addresses whose tokens we have already issued. */ 28 | mapping(address => bool) public issued; 29 | 30 | /** Centrally issued token we are distributing to our contributors */ 31 | StandardTokenExt public token; 32 | 33 | /** Party (team multisig) who is in the control of the token pool. Note that this will be different from the owner address (scripted) that calls this contract. */ 34 | address public allower; 35 | 36 | /** How many addresses have received their tokens. */ 37 | uint public issuedCount; 38 | 39 | function Issuer(address _owner, address _allower, StandardTokenExt _token) { 40 | owner = _owner; 41 | allower = _allower; 42 | token = _token; 43 | } 44 | 45 | function issue(address benefactor, uint amount) onlyOwner { 46 | if(issued[benefactor]) throw; 47 | token.transferFrom(allower, benefactor, amount); 48 | issued[benefactor] = true; 49 | issuedCount += amount; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /contracts/KYCCrowdsale.sol: -------------------------------------------------------------------------------- 1 | import "./CrowdsaleBase.sol"; 2 | import "./AllocatedCrowdsaleMixin.sol"; 3 | import "./KYCPayloadDeserializer.sol"; 4 | 5 | /** 6 | * A crowdsale that allows only signed payload with server-side specified buy in limits. 7 | * 8 | * 9 | * The token distribution happens as in the allocated crowdsale. 10 | * 11 | */ 12 | contract KYCCrowdsale is AllocatedCrowdsaleMixin, KYCPayloadDeserializer { 13 | 14 | /* Server holds the private key to this address to decide if the AML payload is valid or not. */ 15 | address public signerAddress; 16 | 17 | /* A new server-side signer key was set to be effective */ 18 | event SignerChanged(address signer); 19 | 20 | /** 21 | * Constructor. 22 | */ 23 | function KYCCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal, address _beneficiary) CrowdsaleBase(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) AllocatedCrowdsaleMixin(_beneficiary) { 24 | 25 | } 26 | 27 | /** 28 | * A token purchase with anti-money laundering 29 | * 30 | * ©return tokenAmount How many tokens where bought 31 | */ 32 | function buyWithKYCData(bytes dataframe, uint8 v, bytes32 r, bytes32 s) public payable returns(uint tokenAmount) { 33 | 34 | uint _tokenAmount; 35 | uint multiplier = 10 ** 18; 36 | 37 | // Perform signature check for normal addresses 38 | // (not deployment accounts, etc.) 39 | if(earlyParticipantWhitelist[msg.sender]) { 40 | // For test purchases use this faux customer id 41 | _tokenAmount = investInternal(msg.sender, 0x1000); 42 | 43 | } else { 44 | 45 | bytes32 hash = sha256(dataframe); 46 | 47 | var (whitelistedAddress, customerId, minETH, maxETH) = getKYCPayload(dataframe); 48 | 49 | // Check that the KYC data is signed by our server 50 | require(ecrecover(hash, v, r, s) == signerAddress); 51 | 52 | // Only whitelisted address can participate the transaction 53 | require(whitelistedAddress == msg.sender); 54 | 55 | _tokenAmount = investInternal(msg.sender, customerId); 56 | 57 | } 58 | 59 | if(!earlyParticipantWhitelist[msg.sender]) { 60 | // We assume there is no serious min and max fluctuations for the customer, unless 61 | // especially set in the server side per customer manual override. 62 | // Otherwise the customer can reuse old data payload with different min or max value 63 | // to work around the per customer cap. 64 | require(investedAmountOf[msg.sender] >= minETH * multiplier / 10000); 65 | require(investedAmountOf[msg.sender] <= maxETH * multiplier / 10000); 66 | } 67 | 68 | return _tokenAmount; 69 | } 70 | 71 | /// @dev This function can set the server side address 72 | /// @param _signerAddress The address derived from server's private key 73 | function setSignerAddress(address _signerAddress) onlyOwner { 74 | signerAddress = _signerAddress; 75 | SignerChanged(signerAddress); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /contracts/KYCPayloadDeserializer.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | 8 | import "./BytesDeserializer.sol"; 9 | 10 | /** 11 | * A mix-in contract to decode different AML payloads. 12 | * 13 | * @notice This should be a library, but for the complexity and toolchain fragility risks involving of linking library inside library, we put this as a mix-in. 14 | */ 15 | contract KYCPayloadDeserializer { 16 | 17 | using BytesDeserializer for bytes; 18 | 19 | // The bytes payload set on the server side 20 | // total 56 bytes 21 | 22 | struct KYCPayload { 23 | 24 | /** Customer whitelisted address where the deposit can come from */ 25 | address whitelistedAddress; // 20 bytes 26 | 27 | /** Customer id, UUID v4 */ 28 | uint128 customerId; // 16 bytes 29 | 30 | /** 31 | * Min amount this customer needs to invest in ETH. Set zero if no minimum. Expressed as parts of 10000. 1 ETH = 10000. 32 | * @notice Decided to use 32-bit words to make the copy-pasted Data field for the ICO transaction less lenghty. 33 | */ 34 | uint32 minETH; // 4 bytes 35 | 36 | /** Max amount this customer can to invest in ETH. Set zero if no maximum. Expressed as parts of 10000. 1 ETH = 10000. */ 37 | uint32 maxETH; // 4 bytes 38 | } 39 | 40 | /** 41 | * Deconstruct server-side byte data to structured data. 42 | */ 43 | 44 | function deserializeKYCPayload(bytes dataframe) internal constant returns(KYCPayload decodedPayload) { 45 | KYCPayload payload; 46 | payload.whitelistedAddress = dataframe.sliceAddress(0); 47 | payload.customerId = uint128(dataframe.slice16(20)); 48 | payload.minETH = uint32(dataframe.slice4(36)); 49 | payload.maxETH = uint32(dataframe.slice4(40)); 50 | return payload; 51 | } 52 | 53 | /** 54 | * Helper function to allow us to return the decoded payload to an external caller for testing. 55 | * 56 | * TODO: Some sort of compiler issue (?) with memory keyword. Tested with solc 0.4.16 and solc 0.4.18. 57 | * If used, makes KYCCrowdsale to set itself to a bad state getState() returns 5 (Failure). Overrides some memory? 58 | */ 59 | /* 60 | function broken_getKYCPayload(bytes dataframe) public constant returns(address whitelistedAddress, uint128 customerId, uint32 minEth, uint32 maxEth) { 61 | KYCPayload memory payload = deserializeKYCPayload(dataframe); 62 | payload.whitelistedAddress = dataframe.sliceAddress(0); 63 | payload.customerId = uint128(dataframe.slice16(20)); 64 | payload.minETH = uint32(dataframe.slice4(36)); 65 | payload.maxETH = uint32(dataframe.slice4(40)); 66 | return (payload.whitelistedAddress, payload.customerId, payload.minETH, payload.maxETH); 67 | }*/ 68 | 69 | /** 70 | * Same as above, does not seem to cause any issue. 71 | */ 72 | function getKYCPayload(bytes dataframe) public constant returns(address whitelistedAddress, uint128 customerId, uint32 minEth, uint32 maxEth) { 73 | address _whitelistedAddress = dataframe.sliceAddress(0); 74 | uint128 _customerId = uint128(dataframe.slice16(20)); 75 | uint32 _minETH = uint32(dataframe.slice4(36)); 76 | uint32 _maxETH = uint32(dataframe.slice4(40)); 77 | return (_whitelistedAddress, _customerId, _minETH, _maxETH); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /contracts/MilestonePricing.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | import "./PricingStrategy.sol"; 10 | import "./Crowdsale.sol"; 11 | import "./SafeMathLib.sol"; 12 | import "zeppelin/contracts/ownership/Ownable.sol"; 13 | 14 | 15 | /// @dev Time milestone based pricing with special support for pre-ico deals. 16 | contract MilestonePricing is PricingStrategy, Ownable { 17 | 18 | using SafeMathLib for uint; 19 | 20 | uint public constant MAX_MILESTONE = 10; 21 | 22 | // This contains all pre-ICO addresses, and their prices (weis per token) 23 | mapping (address => uint) public preicoAddresses; 24 | 25 | /** 26 | * Define pricing schedule using milestones. 27 | */ 28 | struct Milestone { 29 | 30 | // UNIX timestamp when this milestone kicks in 31 | uint time; 32 | 33 | // How many tokens per satoshi you will get after this milestone has been passed 34 | uint price; 35 | } 36 | 37 | // Store milestones in a fixed array, so that it can be seen in a blockchain explorer 38 | // Milestone 0 is always (0, 0) 39 | // (TODO: change this when we confirm dynamic arrays are explorable) 40 | Milestone[10] public milestones; 41 | 42 | // How many active milestones we have 43 | uint public milestoneCount; 44 | 45 | /// @dev Contruction, creating a list of milestones 46 | /// @param _milestones uint[] milestones Pairs of (time, price) 47 | function MilestonePricing(uint[] _milestones) { 48 | // Need to have tuples, length check 49 | if(_milestones.length % 2 == 1 || _milestones.length >= MAX_MILESTONE*2) { 50 | throw; 51 | } 52 | 53 | milestoneCount = _milestones.length / 2; 54 | 55 | uint lastTimestamp = 0; 56 | 57 | for(uint i=0; i<_milestones.length/2; i++) { 58 | milestones[i].time = _milestones[i*2]; 59 | milestones[i].price = _milestones[i*2+1]; 60 | 61 | // No invalid steps 62 | if((lastTimestamp != 0) && (milestones[i].time <= lastTimestamp)) { 63 | throw; 64 | } 65 | 66 | lastTimestamp = milestones[i].time; 67 | } 68 | 69 | // Last milestone price must be zero, terminating the crowdale 70 | if(milestones[milestoneCount-1].price != 0) { 71 | throw; 72 | } 73 | } 74 | 75 | /// @dev This is invoked once for every pre-ICO address, set pricePerToken 76 | /// to 0 to disable 77 | /// @param preicoAddress PresaleFundCollector address 78 | /// @param pricePerToken How many weis one token cost for pre-ico investors 79 | function setPreicoAddress(address preicoAddress, uint pricePerToken) 80 | public 81 | onlyOwner 82 | { 83 | preicoAddresses[preicoAddress] = pricePerToken; 84 | } 85 | 86 | /// @dev Iterate through milestones. You reach end of milestones when price = 0 87 | /// @return tuple (time, price) 88 | function getMilestone(uint n) public constant returns (uint, uint) { 89 | return (milestones[n].time, milestones[n].price); 90 | } 91 | 92 | function getFirstMilestone() private constant returns (Milestone) { 93 | return milestones[0]; 94 | } 95 | 96 | function getLastMilestone() private constant returns (Milestone) { 97 | return milestones[milestoneCount-1]; 98 | } 99 | 100 | function getPricingStartsAt() public constant returns (uint) { 101 | return getFirstMilestone().time; 102 | } 103 | 104 | function getPricingEndsAt() public constant returns (uint) { 105 | return getLastMilestone().time; 106 | } 107 | 108 | function isSane(address _crowdsale) public constant returns(bool) { 109 | Crowdsale crowdsale = Crowdsale(_crowdsale); 110 | return crowdsale.startsAt() == getPricingStartsAt() && crowdsale.endsAt() == getPricingEndsAt(); 111 | } 112 | 113 | /// @dev Get the current milestone or bail out if we are not in the milestone periods. 114 | /// @return {[type]} [description] 115 | function getCurrentMilestone() private constant returns (Milestone) { 116 | uint i; 117 | 118 | for(i=0; i 0) { 138 | return value.times(multiplier) / preicoAddresses[msgSender]; 139 | } 140 | 141 | uint price = getCurrentPrice(); 142 | return value.times(multiplier) / price; 143 | } 144 | 145 | function isPresalePurchase(address purchaser) public constant returns (bool) { 146 | if(preicoAddresses[purchaser] > 0) 147 | return true; 148 | else 149 | return false; 150 | } 151 | 152 | function() payable { 153 | throw; // No money on this contract 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /contracts/MintableToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | import "zeppelin/contracts/token/ERC20.sol"; 8 | import "zeppelin/contracts/ownership/Ownable.sol"; 9 | import "./StandardTokenExt.sol"; 10 | import "./SafeMathLib.sol"; 11 | 12 | pragma solidity ^0.4.6; 13 | 14 | /** 15 | * A token that can increase its supply by another contract. 16 | * 17 | * This allows uncapped crowdsale by dynamically increasing the supply when money pours in. 18 | * Only mint agents, contracts whitelisted by owner, can mint new tokens. 19 | * 20 | */ 21 | contract MintableToken is StandardTokenExt, Ownable { 22 | 23 | using SafeMathLib for uint; 24 | 25 | bool public mintingFinished = false; 26 | 27 | /** List of agents that are allowed to create new tokens */ 28 | mapping (address => bool) public mintAgents; 29 | 30 | event MintingAgentChanged(address addr, bool state); 31 | event Minted(address receiver, uint amount); 32 | 33 | /** 34 | * Create new tokens and allocate them to an address.. 35 | * 36 | * Only callably by a crowdsale contract (mint agent). 37 | */ 38 | function mint(address receiver, uint amount) onlyMintAgent canMint public { 39 | totalSupply = totalSupply.plus(amount); 40 | balances[receiver] = balances[receiver].plus(amount); 41 | 42 | // This will make the mint transaction apper in EtherScan.io 43 | // We can remove this after there is a standardized minting event 44 | Transfer(0, receiver, amount); 45 | } 46 | 47 | /** 48 | * Owner can allow a crowdsale contract to mint new tokens. 49 | */ 50 | function setMintAgent(address addr, bool state) onlyOwner canMint public { 51 | mintAgents[addr] = state; 52 | MintingAgentChanged(addr, state); 53 | } 54 | 55 | modifier onlyMintAgent() { 56 | // Only crowdsale contracts are allowed to mint new tokens 57 | if(!mintAgents[msg.sender]) { 58 | throw; 59 | } 60 | _; 61 | } 62 | 63 | /** Make sure we are not done yet. */ 64 | modifier canMint() { 65 | if(mintingFinished) throw; 66 | _; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/MintedEthCappedCrowdsale.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./Crowdsale.sol"; 10 | import "./MintableToken.sol"; 11 | 12 | /** 13 | * ICO crowdsale contract that is capped by amout of ETH. 14 | * 15 | * - Tokens are dynamically created during the crowdsale 16 | * 17 | * 18 | */ 19 | contract MintedEthCappedCrowdsale is Crowdsale { 20 | 21 | /* Maximum amount of wei this crowdsale can raise. */ 22 | uint public weiCap; 23 | 24 | function MintedEthCappedCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal, uint _weiCap) Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) { 25 | weiCap = _weiCap; 26 | } 27 | 28 | /** 29 | * Called from invest() to confirm if the curret investment does not break our cap rule. 30 | */ 31 | function isBreakingCap(uint weiAmount, uint tokenAmount, uint weiRaisedTotal, uint tokensSoldTotal) constant returns (bool limitBroken) { 32 | return weiRaisedTotal > weiCap; 33 | } 34 | 35 | function isCrowdsaleFull() public constant returns (bool) { 36 | return weiRaised >= weiCap; 37 | } 38 | 39 | /** 40 | * Dynamically create tokens and assign them to the investor. 41 | */ 42 | function assignTokens(address receiver, uint tokenAmount) internal { 43 | MintableToken mintableToken = MintableToken(token); 44 | mintableToken.mint(receiver, tokenAmount); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/MintedTokenCappedCrowdsale.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./Crowdsale.sol"; 10 | import "./MintableToken.sol"; 11 | 12 | /** 13 | * ICO crowdsale contract that is capped by amout of tokens. 14 | * 15 | * - Tokens are dynamically created during the crowdsale 16 | * 17 | * 18 | */ 19 | contract MintedTokenCappedCrowdsale is Crowdsale { 20 | 21 | /* Maximum amount of tokens this crowdsale can sell. */ 22 | uint public maximumSellableTokens; 23 | 24 | function MintedTokenCappedCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal, uint _maximumSellableTokens) Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) { 25 | maximumSellableTokens = _maximumSellableTokens; 26 | } 27 | 28 | /** 29 | * Called from invest() to confirm if the curret investment does not break our cap rule. 30 | */ 31 | function isBreakingCap(uint weiAmount, uint tokenAmount, uint weiRaisedTotal, uint tokensSoldTotal) constant returns (bool limitBroken) { 32 | return tokensSoldTotal > maximumSellableTokens; 33 | } 34 | 35 | function isCrowdsaleFull() public constant returns (bool) { 36 | return tokensSold >= maximumSellableTokens; 37 | } 38 | 39 | /** 40 | * Dynamically create tokens and assign them to the investor. 41 | */ 42 | function assignTokens(address receiver, uint tokenAmount) internal { 43 | MintableToken mintableToken = MintableToken(token); 44 | mintableToken.mint(receiver, tokenAmount); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/NullFinalizeAgent.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | import "./Crowdsale.sol"; 10 | import "./ReleasableToken.sol"; 11 | 12 | /** 13 | * A finalize agent that does nothing. 14 | * 15 | * - Token transfer must be manually released by the owner 16 | */ 17 | contract NullFinalizeAgent is FinalizeAgent { 18 | 19 | Crowdsale public crowdsale; 20 | 21 | function NullFinalizeAgent(Crowdsale _crowdsale) { 22 | crowdsale = _crowdsale; 23 | } 24 | 25 | /** Check that we can release the token */ 26 | function isSane() public constant returns (bool) { 27 | return true; 28 | } 29 | 30 | /** Called once by crowdsale finalize() if the sale was success. */ 31 | function finalizeCrowdsale() public { 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /contracts/PaymentForwarder.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./Haltable.sol"; 10 | 11 | /** 12 | * Forward Ethereum payments to another wallet and track them with an event. 13 | * 14 | * Allows to identify customers who made Ethereum payment for a central token issuance. 15 | * Furthermore allow making a payment on behalf of another address. 16 | * 17 | * Allow pausing to signal the end of the crowdsale. 18 | */ 19 | contract PaymentForwarder is Haltable { 20 | 21 | /** Who will get all ETH in the end */ 22 | address public teamMultisig; 23 | 24 | /** Total incoming money */ 25 | uint public totalTransferred; 26 | 27 | /** How many distinct customers we have that have made a payment */ 28 | uint public customerCount; 29 | 30 | /** Total incoming money per centrally tracked customer id */ 31 | mapping(uint128 => uint) public paymentsByCustomer; 32 | 33 | /** Total incoming money per benefactor address */ 34 | mapping(address => uint) public paymentsByBenefactor; 35 | 36 | /** A customer has made a payment. Benefactor is the address where the tokens will be ultimately issued.*/ 37 | event PaymentForwarded(address source, uint amount, uint128 customerId, address benefactor); 38 | 39 | function PaymentForwarder(address _owner, address _teamMultisig) { 40 | teamMultisig = _teamMultisig; 41 | owner = _owner; 42 | } 43 | 44 | function payWithoutChecksum(uint128 customerId, address benefactor) public stopInEmergency payable { 45 | 46 | uint weiAmount = msg.value; 47 | 48 | PaymentForwarded(msg.sender, weiAmount, customerId, benefactor); 49 | 50 | // We trust Ethereum amounts cannot overflow uint256 51 | totalTransferred += weiAmount; 52 | 53 | if(paymentsByCustomer[customerId] == 0) { 54 | customerCount++; 55 | } 56 | 57 | paymentsByCustomer[customerId] += weiAmount; 58 | 59 | // We track benefactor addresses for extra safety; 60 | // In the case of central ETH issuance tracking has problems we can 61 | // construct ETH contributions solely based on blockchain data 62 | paymentsByBenefactor[benefactor] += weiAmount; 63 | 64 | // May run out of gas 65 | if(!teamMultisig.send(weiAmount)) throw; 66 | } 67 | 68 | /** 69 | * Pay on a behalf of an address. 70 | * 71 | * @param customerId Identifier in the central database, UUID v4 72 | * 73 | */ 74 | function pay(uint128 customerId, address benefactor, bytes1 checksum) public stopInEmergency payable { 75 | // see customerid.py 76 | if (bytes1(sha3(customerId, benefactor)) != checksum) throw; 77 | payWithoutChecksum(customerId, benefactor); 78 | } 79 | 80 | /** 81 | * Pay on a behalf of the sender. 82 | * 83 | * @param customerId Identifier in the central database, UUID v4 84 | * 85 | */ 86 | function payForMyselfWithChecksum(uint128 customerId, bytes1 checksum) public payable { 87 | // see customerid.py 88 | if (bytes1(sha3(customerId)) != checksum) throw; 89 | payWithoutChecksum(customerId, msg.sender); 90 | } 91 | 92 | /** 93 | * Legacy API signature. 94 | */ 95 | function payForMyself(uint128 customerId) public payable { 96 | payWithoutChecksum(customerId, msg.sender); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /contracts/PresaleFundCollector.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | 10 | import "./Crowdsale.sol"; 11 | import "./SafeMathLib.sol"; 12 | 13 | /** 14 | * Collect funds from presale investors to be send to the crowdsale smart contract later. 15 | * 16 | * - Collect funds from pre-sale investors 17 | * - Send funds to the crowdsale when it opens 18 | * - Allow owner to set the crowdsale 19 | * - Have refund after X days as a safety hatch if the crowdsale doesn't materilize 20 | * 21 | */ 22 | contract PresaleFundCollector is Ownable { 23 | 24 | using SafeMathLib for uint; 25 | 26 | /** How many investors when can carry per a single contract */ 27 | uint public MAX_INVESTORS = 32; 28 | 29 | /** How many investors we have now */ 30 | uint public investorCount; 31 | 32 | /** Who are our investors (iterable) */ 33 | address[] public investors; 34 | 35 | /** How much they have invested */ 36 | mapping(address => uint) public balances; 37 | 38 | /** When our refund freeze is over (UNIX timestamp) */ 39 | uint public freezeEndsAt; 40 | 41 | /** What is the minimum buy in */ 42 | uint public weiMinimumLimit; 43 | 44 | /** Have we begun to move funds */ 45 | bool public moving; 46 | 47 | /** Our ICO contract where we will move the funds */ 48 | Crowdsale public crowdsale; 49 | 50 | event Invested(address investor, uint value); 51 | event Refunded(address investor, uint value); 52 | 53 | /** 54 | * Create presale contract where lock up period is given days 55 | */ 56 | function PresaleFundCollector(address _owner, uint _freezeEndsAt, uint _weiMinimumLimit) { 57 | 58 | owner = _owner; 59 | 60 | // Give argument 61 | if(_freezeEndsAt == 0) { 62 | throw; 63 | } 64 | 65 | // Give argument 66 | if(_weiMinimumLimit == 0) { 67 | throw; 68 | } 69 | 70 | weiMinimumLimit = _weiMinimumLimit; 71 | freezeEndsAt = _freezeEndsAt; 72 | } 73 | 74 | /** 75 | * Participate to a presale. 76 | */ 77 | function invest() public payable { 78 | 79 | // Cannot invest anymore through crowdsale when moving has begun 80 | if(moving) throw; 81 | 82 | address investor = msg.sender; 83 | 84 | bool existing = balances[investor] > 0; 85 | 86 | balances[investor] = balances[investor].plus(msg.value); 87 | 88 | // Need to fulfill minimum limit 89 | if(balances[investor] < weiMinimumLimit) { 90 | throw; 91 | } 92 | 93 | // This is a new investor 94 | if(!existing) { 95 | 96 | // Limit number of investors to prevent too long loops 97 | if(investorCount >= MAX_INVESTORS) throw; 98 | 99 | investors.push(investor); 100 | investorCount++; 101 | } 102 | 103 | Invested(investor, msg.value); 104 | } 105 | 106 | /** 107 | * Load funds to the crowdsale for a single investor. 108 | */ 109 | function participateCrowdsaleInvestor(address investor) public { 110 | 111 | // Crowdsale not yet set 112 | if(address(crowdsale) == 0) throw; 113 | 114 | moving = true; 115 | 116 | if(balances[investor] > 0) { 117 | uint amount = balances[investor]; 118 | delete balances[investor]; 119 | crowdsale.invest.value(amount)(investor); 120 | } 121 | } 122 | 123 | /** 124 | * Load funds to the crowdsale for all investor. 125 | * 126 | */ 127 | function participateCrowdsaleAll() public { 128 | 129 | // We might hit a max gas limit in this loop, 130 | // and in this case you can simply call participateCrowdsaleInvestor() for all investors 131 | for(uint i=0; i bool) public reissuedTransactions; 26 | 27 | function RelaunchedCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal, uint _maximumSellableTokens) MintedTokenCappedCrowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal, _maximumSellableTokens) { 28 | } 29 | 30 | /** 31 | * Check if a particular transaction has already been written. 32 | */ 33 | function getRestoredTransactionStatus(uint _originalTxHash) public constant returns(bool) { 34 | return reissuedTransactions[_originalTxHash]; 35 | } 36 | 37 | /** 38 | * Rebuild the previous invest data back to the crowdsale. 39 | */ 40 | function setInvestorData(address _addr, uint _weiAmount, uint _tokenAmount, uint _originalTxHash) onlyOwner public { 41 | 42 | if(investedAmountOf[_addr] == 0) { 43 | investorCount++; 44 | } 45 | 46 | investedAmountOf[_addr] += _weiAmount; 47 | tokenAmountOf[_addr] += _tokenAmount; 48 | 49 | weiRaised += _weiAmount; 50 | tokensSold += _tokenAmount; 51 | 52 | Invested(_addr, _weiAmount, _tokenAmount, 0); 53 | RestoredInvestment(_addr, _originalTxHash); 54 | } 55 | 56 | /** 57 | * Rebuild the previous invest data and do a token reissuance. 58 | */ 59 | function setInvestorDataAndIssueNewToken(address _addr, uint _weiAmount, uint _tokenAmount, uint _originalTxHash) onlyOwner public { 60 | 61 | // This transaction has already been rebuild 62 | if(reissuedTransactions[_originalTxHash]) { 63 | throw; 64 | } 65 | 66 | setInvestorData(_addr, _weiAmount, _tokenAmount, _originalTxHash); 67 | 68 | // Check that we did not bust the cap in the restoration process 69 | if(isBreakingCap(_weiAmount, _tokenAmount, weiRaised, tokensSold)) { 70 | throw; 71 | } 72 | 73 | // Mark transaction processed 74 | reissuedTransactions[_originalTxHash] = true; 75 | 76 | // Mint new token to give it to the original investor 77 | MintableToken mintableToken = MintableToken(token); 78 | mintableToken.mint(_addr, _tokenAmount); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /contracts/ReleasableToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "zeppelin/contracts/ownership/Ownable.sol"; 10 | import "zeppelin/contracts/token/ERC20.sol"; 11 | 12 | 13 | /** 14 | * Define interface for releasing the token transfer after a successful crowdsale. 15 | */ 16 | contract ReleasableToken is ERC20, Ownable { 17 | 18 | /* The finalizer contract that allows unlift the transfer limits on this token */ 19 | address public releaseAgent; 20 | 21 | /** A crowdsale contract can release us to the wild if ICO success. If false we are are in transfer lock up period.*/ 22 | bool public released = false; 23 | 24 | /** 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. */ 25 | mapping (address => bool) public transferAgents; 26 | 27 | /** 28 | * Limit token transfer until the crowdsale is over. 29 | * 30 | */ 31 | modifier canTransfer(address _sender) { 32 | 33 | if(!released) { 34 | if(!transferAgents[_sender]) { 35 | throw; 36 | } 37 | } 38 | 39 | _; 40 | } 41 | 42 | /** 43 | * Set the contract that can call release and make the token transferable. 44 | * 45 | * Design choice. Allow reset the release agent to fix fat finger mistakes. 46 | */ 47 | function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public { 48 | 49 | // We don't do interface check here as we might want to a normal wallet address to act as a release agent 50 | releaseAgent = addr; 51 | } 52 | 53 | /** 54 | * Owner can allow a particular address (a crowdsale contract) to transfer tokens despite the lock up period. 55 | */ 56 | function setTransferAgent(address addr, bool state) onlyOwner inReleaseState(false) public { 57 | transferAgents[addr] = state; 58 | } 59 | 60 | /** 61 | * One way function to release the tokens to the wild. 62 | * 63 | * 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). 64 | */ 65 | function releaseTokenTransfer() public onlyReleaseAgent { 66 | released = true; 67 | } 68 | 69 | /** The function can be called only before or after the tokens have been releasesd */ 70 | modifier inReleaseState(bool releaseState) { 71 | if(releaseState != released) { 72 | throw; 73 | } 74 | _; 75 | } 76 | 77 | /** The function can be called only by a whitelisted release agent. */ 78 | modifier onlyReleaseAgent() { 79 | if(msg.sender != releaseAgent) { 80 | throw; 81 | } 82 | _; 83 | } 84 | 85 | function transfer(address _to, uint _value) canTransfer(msg.sender) returns (bool success) { 86 | // Call StandardToken.transfer() 87 | return super.transfer(_to, _value); 88 | } 89 | 90 | function transferFrom(address _from, address _to, uint _value) canTransfer(_from) returns (bool success) { 91 | // Call StandardToken.transferForm() 92 | return super.transferFrom(_from, _to, _value); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /contracts/SafeMathLib.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | /** 10 | * Safe unsigned safe math. 11 | * 12 | * https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736#.750gwtwli 13 | * 14 | * Originally from https://raw.githubusercontent.com/AragonOne/zeppelin-solidity/master/contracts/SafeMathLib.sol 15 | * 16 | * Maintained here until merged to mainline zeppelin-solidity. 17 | * 18 | */ 19 | library SafeMathLib { 20 | 21 | function times(uint a, uint b) returns (uint) { 22 | uint c = a * b; 23 | assert(a == 0 || c / a == b); 24 | return c; 25 | } 26 | 27 | function minus(uint a, uint b) returns (uint) { 28 | assert(b <= a); 29 | return a - b; 30 | } 31 | 32 | function plus(uint a, uint b) returns (uint) { 33 | uint c = a + b; 34 | assert(c>=a); 35 | return c; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /contracts/StandardTokenExt.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.14; 8 | 9 | import "zeppelin/contracts/token/StandardToken.sol"; 10 | 11 | 12 | /** 13 | * Standard EIP-20 token with an interface marker. 14 | * 15 | * @notice Interface marker is used by crowdsale contracts to validate that addresses point a good token contract. 16 | * 17 | */ 18 | contract StandardTokenExt is StandardToken { 19 | 20 | /* Interface declaration */ 21 | function isToken() public constant returns (bool weAre) { 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/TimeVault.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./StandardTokenExt.sol"; 10 | 11 | /** 12 | * 13 | * Time-locked token vault of allocated founder tokens. 14 | * 15 | * First used by Lunyr https://github.com/Lunyr/crowdsale-contracts 16 | * 17 | * 18 | * See TokenVault for multi user implementation. 19 | */ 20 | contract TimeVault { 21 | 22 | /** Interface flag to determine if address is for a real contract or not */ 23 | bool public isTimeVault = true; 24 | 25 | /** Token we are holding */ 26 | StandardTokenExt public token; 27 | 28 | /** Address that can claim tokens */ 29 | address public teamMultisig; 30 | 31 | /** UNIX timestamp when tokens can be claimed. */ 32 | uint256 public unlockedAt; 33 | 34 | event Unlocked(); 35 | 36 | function TimeVault(address _teamMultisig, StandardTokenExt _token, uint _unlockedAt) { 37 | 38 | teamMultisig = _teamMultisig; 39 | token = _token; 40 | unlockedAt = _unlockedAt; 41 | 42 | // Sanity check 43 | if (teamMultisig == 0x0) throw; 44 | if (address(token) == 0x0) throw; 45 | } 46 | 47 | function getTokenBalance() public constant returns (uint) { 48 | return token.balanceOf(address(this)); 49 | } 50 | 51 | function unlock() public { 52 | // Wait your turn! 53 | if (now < unlockedAt) throw; 54 | 55 | // StandardToken will throw in the case of transaction fails 56 | token.transfer(teamMultisig, getTokenBalance()); 57 | 58 | Unlocked(); 59 | } 60 | 61 | // disallow ETH payment for this vault 62 | function () { throw; } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /contracts/TokenTranchePricing.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | import "./PricingStrategy.sol"; 10 | import "./Crowdsale.sol"; 11 | import "./SafeMathLib.sol"; 12 | import "zeppelin/contracts/ownership/Ownable.sol"; 13 | 14 | /// @dev Tranche based pricing with special support for pre-ico deals. 15 | /// Implementing "first price" tranches, meaning, that if buyers order is 16 | /// covering more than one tranche, the price of the lowest tranche will apply 17 | /// to the whole order. 18 | contract TokenTranchePricing is PricingStrategy, Ownable { 19 | 20 | using SafeMathLib for uint; 21 | 22 | uint public constant MAX_TRANCHES = 10; 23 | 24 | // This contains all pre-ICO addresses, and their prices (weis per token) 25 | mapping (address => uint) public preicoAddresses; 26 | 27 | /** 28 | * Define pricing schedule using tranches. 29 | */ 30 | struct Tranche { 31 | 32 | // Amount in weis when this tranche becomes active 33 | uint amount; 34 | 35 | // How many tokens per satoshi you will get while this tranche is active 36 | uint price; 37 | } 38 | 39 | // Store tranches in a fixed array, so that it can be seen in a blockchain explorer 40 | // Tranche 0 is always (0, 0) 41 | // (TODO: change this when we confirm dynamic arrays are explorable) 42 | Tranche[10] public tranches; 43 | 44 | // How many active tranches we have 45 | uint public trancheCount; 46 | 47 | /// @dev Contruction, creating a list of tranches 48 | /// @param _tranches uint[] tranches Pairs of (start amount, price) 49 | function TokenTranchePricing(uint[] _tranches) { 50 | // Need to have tuples, length check 51 | if(_tranches.length % 2 == 1 || _tranches.length >= MAX_TRANCHES*2) { 52 | throw; 53 | } 54 | 55 | trancheCount = _tranches.length / 2; 56 | 57 | uint highestAmount = 0; 58 | 59 | for(uint i=0; i<_tranches.length/2; i++) { 60 | tranches[i].amount = _tranches[i*2]; 61 | tranches[i].price = _tranches[i*2+1]; 62 | 63 | // No invalid steps 64 | if((highestAmount != 0) && (tranches[i].amount <= highestAmount)) { 65 | throw; 66 | } 67 | 68 | highestAmount = tranches[i].amount; 69 | } 70 | 71 | // Last tranche price must be zero, terminating the crowdale 72 | if(tranches[trancheCount-1].price != 0) { 73 | throw; 74 | } 75 | } 76 | 77 | /// @dev This is invoked once for every pre-ICO address, set pricePerToken 78 | /// to 0 to disable 79 | /// @param preicoAddress PresaleFundCollector address 80 | /// @param pricePerToken How many weis one token cost for pre-ico investors 81 | function setPreicoAddress(address preicoAddress, uint pricePerToken) 82 | public 83 | onlyOwner 84 | { 85 | preicoAddresses[preicoAddress] = pricePerToken; 86 | } 87 | 88 | /// @dev Iterate through tranches. You reach end of tranches when price = 0 89 | /// @return tuple (time, price) 90 | function getTranche(uint n) public constant returns (uint, uint) { 91 | return (tranches[n].amount, tranches[n].price); 92 | } 93 | 94 | function getFirstTranche() private constant returns (Tranche) { 95 | return tranches[0]; 96 | } 97 | 98 | function getLastTranche() private constant returns (Tranche) { 99 | return tranches[trancheCount-1]; 100 | } 101 | 102 | function getPricingStartsAt() public constant returns (uint) { 103 | return getFirstTranche().amount; 104 | } 105 | 106 | function getPricingEndsAt() public constant returns (uint) { 107 | return getLastTranche().amount; 108 | } 109 | 110 | function isSane(address _crowdsale) public constant returns(bool) { 111 | // Our tranches are not bound by time, so we can't really check are we sane 112 | // so we presume we are ;) 113 | // In the future we could save and track raised tokens, and compare it to 114 | // the Crowdsale contract. 115 | return true; 116 | } 117 | 118 | /// @dev Get the current tranche or bail out if we are not in the tranche periods. 119 | /// @param tokensSold total amount of tokens sold, for calculating the current tranche 120 | /// @return {[type]} [description] 121 | function getCurrentTranche(uint tokensSold) private constant returns (Tranche) { 122 | uint i; 123 | 124 | for(i=0; i < tranches.length; i++) { 125 | if(tokensSold < tranches[i].amount) { 126 | return tranches[i-1]; 127 | } 128 | } 129 | } 130 | 131 | /// @dev Get the current price. 132 | /// @param tokensSold total amount of tokens sold, for calculating the current tranche 133 | /// @return The current price or 0 if we are outside trache ranges 134 | function getCurrentPrice(uint tokensSold) public constant returns (uint result) { 135 | return getCurrentTranche(tokensSold).price; 136 | } 137 | 138 | /// @dev Calculate the current price for buy in amount. 139 | function calculatePrice(uint value, uint weiRaised, uint tokensSold, address msgSender, uint decimals) public constant returns (uint) { 140 | 141 | uint multiplier = 10 ** decimals; 142 | 143 | // This investor is coming through pre-ico 144 | if(preicoAddresses[msgSender] > 0) { 145 | return value.times(multiplier) / preicoAddresses[msgSender]; 146 | } 147 | 148 | uint price = getCurrentPrice(tokensSold); 149 | return value.times(multiplier) / price; 150 | } 151 | 152 | function() payable { 153 | throw; // No money on this contract 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /contracts/UncappedCrowdsale.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "./Crowdsale.sol"; 10 | import "./MintableToken.sol"; 11 | 12 | 13 | /** 14 | * Uncapped ICO crowdsale contract. 15 | * 16 | * 17 | * Intended usage 18 | * 19 | * - A short time window 20 | * - Flat price 21 | * - No cap 22 | * 23 | */ 24 | contract UncappedCrowdsale is Crowdsale { 25 | 26 | function UncappedCrowdsale(address _token, PricingStrategy _pricingStrategy, address _multisigWallet, uint _start, uint _end, uint _minimumFundingGoal) Crowdsale(_token, _pricingStrategy, _multisigWallet, _start, _end, _minimumFundingGoal) { 27 | 28 | } 29 | 30 | /** 31 | * Called from invest() to confirm if the curret investment does not break our cap rule. 32 | */ 33 | function isBreakingCap(uint weiAmount, uint tokenAmount, uint weiRaisedTotal, uint tokensSoldTotal) constant returns (bool limitBroken) { 34 | return false; 35 | } 36 | 37 | function isCrowdsaleFull() public constant returns (bool) { 38 | // Uncle Scrooge 39 | return false; 40 | } 41 | 42 | function assignTokens(address receiver, uint tokenAmount) internal { 43 | MintableToken mintableToken = MintableToken(token); 44 | mintableToken.mint(receiver, tokenAmount); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/UpgradeAgent.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.6; 8 | 9 | /** 10 | * Upgrade agent interface inspired by Lunyr. 11 | * 12 | * Upgrade agent transfers tokens to a new contract. 13 | * Upgrade agent itself can be the token contract, or just a middle man contract doing the heavy lifting. 14 | */ 15 | contract UpgradeAgent { 16 | 17 | uint public originalSupply; 18 | 19 | /** Interface marker */ 20 | function isUpgradeAgent() public constant returns (bool) { 21 | return true; 22 | } 23 | 24 | function upgradeFrom(address _from, uint256 _value) public; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /contracts/UpgradeableToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net 3 | * 4 | * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt 5 | */ 6 | 7 | pragma solidity ^0.4.8; 8 | 9 | import "zeppelin/contracts/token/ERC20.sol"; 10 | import "./StandardTokenExt.sol"; 11 | import "./UpgradeAgent.sol"; 12 | 13 | /** 14 | * A token upgrade mechanism where users can opt-in amount of tokens to the next smart contract revision. 15 | * 16 | * First envisioned by Golem and Lunyr projects. 17 | */ 18 | contract UpgradeableToken is StandardTokenExt { 19 | 20 | /** 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. */ 21 | address public upgradeMaster; 22 | 23 | /** The next contract where the tokens will be migrated. */ 24 | UpgradeAgent public upgradeAgent; 25 | 26 | /** How many tokens we have upgraded by now. */ 27 | uint256 public totalUpgraded; 28 | 29 | /** 30 | * Upgrade states. 31 | * 32 | * - NotAllowed: The child contract has not reached a condition where the upgrade can bgun 33 | * - WaitingForAgent: Token allows upgrade, but we don't have a new agent yet 34 | * - ReadyToUpgrade: The agent is set, but not a single token has been upgraded yet 35 | * - Upgrading: Upgrade agent is set and the balance holders can upgrade their tokens 36 | * 37 | */ 38 | enum UpgradeState {Unknown, NotAllowed, WaitingForAgent, ReadyToUpgrade, Upgrading} 39 | 40 | /** 41 | * Somebody has upgraded some of his tokens. 42 | */ 43 | event Upgrade(address indexed _from, address indexed _to, uint256 _value); 44 | 45 | /** 46 | * New upgrade agent available. 47 | */ 48 | event UpgradeAgentSet(address agent); 49 | 50 | /** 51 | * Do not allow construction without upgrade master set. 52 | */ 53 | function UpgradeableToken(address _upgradeMaster) { 54 | upgradeMaster = _upgradeMaster; 55 | } 56 | 57 | /** 58 | * Allow the token holder to upgrade some of their tokens to a new contract. 59 | */ 60 | function upgrade(uint256 value) public { 61 | 62 | UpgradeState state = getUpgradeState(); 63 | if(!(state == UpgradeState.ReadyToUpgrade || state == UpgradeState.Upgrading)) { 64 | // Called in a bad state 65 | throw; 66 | } 67 | 68 | // Validate input value. 69 | if (value == 0) throw; 70 | 71 | balances[msg.sender] = balances[msg.sender].sub(value); 72 | 73 | // Take tokens out from circulation 74 | totalSupply = totalSupply.sub(value); 75 | totalUpgraded = totalUpgraded.add(value); 76 | 77 | // Upgrade agent reissues the tokens 78 | upgradeAgent.upgradeFrom(msg.sender, value); 79 | Upgrade(msg.sender, upgradeAgent, value); 80 | } 81 | 82 | /** 83 | * Set an upgrade agent that handles 84 | */ 85 | function setUpgradeAgent(address agent) external { 86 | 87 | if(!canUpgrade()) { 88 | // The token is not yet in a state that we could think upgrading 89 | throw; 90 | } 91 | 92 | if (agent == 0x0) throw; 93 | // Only a master can designate the next agent 94 | if (msg.sender != upgradeMaster) throw; 95 | // Upgrade has already begun for an agent 96 | if (getUpgradeState() == UpgradeState.Upgrading) throw; 97 | 98 | upgradeAgent = UpgradeAgent(agent); 99 | 100 | // Bad interface 101 | if(!upgradeAgent.isUpgradeAgent()) throw; 102 | // Make sure that token supplies match in source and target 103 | if (upgradeAgent.originalSupply() != totalSupply) throw; 104 | 105 | UpgradeAgentSet(upgradeAgent); 106 | } 107 | 108 | /** 109 | * Get the state of the token upgrade. 110 | */ 111 | function getUpgradeState() public constant returns(UpgradeState) { 112 | if(!canUpgrade()) return UpgradeState.NotAllowed; 113 | else if(address(upgradeAgent) == 0x00) return UpgradeState.WaitingForAgent; 114 | else if(totalUpgraded == 0) return UpgradeState.ReadyToUpgrade; 115 | else return UpgradeState.Upgrading; 116 | } 117 | 118 | /** 119 | * Change the upgrade master. 120 | * 121 | * This allows us to set a new owner for the upgrade mechanism. 122 | */ 123 | function setUpgradeMaster(address master) public { 124 | if (master == 0x0) throw; 125 | if (msg.sender != upgradeMaster) throw; 126 | upgradeMaster = master; 127 | } 128 | 129 | /** 130 | * Child contract can enable to provide the condition when the upgrade can begun. 131 | */ 132 | function canUpgrade() public constant returns(bool) { 133 | return true; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /crowdsales/bitrewards-ico.yml: -------------------------------------------------------------------------------- 1 | kovan: 2 | chain: kovan 3 | verify_on_etherscan: yes 4 | browser_driver: chrome 5 | solc: 6 | version: v0.4.14+commit.c2215d46 7 | optimizations: 8 | optimizer: true 9 | runs: 500 10 | contracts: 11 | team_multisig: 12 | contract_name: MultiSigWallet 13 | contract_file: GnosisWallet.sol 14 | address: "0x8a5036176f957af5ad802fb33e218cab00fc98ac" 15 | token: 16 | contract_name: BitrewardsToken 17 | contract_file: BitrewardsToken.sol 18 | arguments: 19 | _name: "BitReward" 20 | _symbol: "BIT" 21 | _initialSupply: 0 22 | _decimals: 18 23 | _mintable: true 24 | crowdsale: 25 | contract_name: MintedTokenCappedCrowdsale 26 | contract_file: MintedTokenCappedCrowdsale.sol 27 | arguments: 28 | _token: "{{contracts.token.address}}" 29 | _pricingStrategy: "{{contracts.pricing_strategy.address}}" 30 | _multisigWallet: "{{contracts.team_multisig.address}}" 31 | _start: "{{ timestamp(datetime(2018, 3, 1, 0, 0)) }}" 32 | _end: "{{ timestamp(datetime(2018, 5, 10, 0, 0)) }}" 33 | _minimumFundingGoal: 40000000 34 | _maximumSellableTokens: 1040000000 35 | finalize_agent: 36 | contract_name: NullFinalizeAgent 37 | contract_file: NullFinalizeAgent.sol 38 | arguments: 39 | _crowdsale: "{{contracts.crowdsale.address}}" 40 | post_actions: | 41 | token.transact({"from": deploy_address}).setTransferAgent(team_multisig.address, True) 42 | token.transact({"from": deploy_address}).setTransferAgent(crowdsale.address, True) 43 | token.transact({"from": deploy_address}).setTransferAgent(finalize_agent.address, True) 44 | token.transact({"from": deploy_address}).setTransferAgent(deploy_address, True) 45 | confirm_tx(crowdsale.transact({"from": deploy_address}).setFinalizeAgent(finalize_agent.address)) 46 | confirm_tx(token.transact({"from": deploy_address}).setReleaseAgent(deploy_address)) 47 | confirm_tx(token.transact({"from": deploy_address}).setUpgradeMaster(team_multisig.address)) 48 | confirm_multiple_txs( \ 49 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist(deploy_address, True), \ 50 | crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist("0x6d997eDcA04282950416FA380d834f360fC36eBb", True) \ 51 | ) 52 | confirm_tx(pricing_strategy.transact({"from": deploy_address}).setPreicoAddress("0x6d997eDcA04282950416FA380d834f360fC36eBb", 2083333333333)) 53 | verify_actions: | 54 | assert token.call().owner().lower() == deploy_address.lower() 55 | assert token.call().released() == False 56 | assert crowdsale.call().owner().lower() == deploy_address.lower() 57 | assert crowdsale.call().multisigWallet().lower() == team_multisig.address.lower() 58 | assert finalize_agent.call().isSane() 59 | assert crowdsale.call().getState() == CrowdsaleState.PreFunding 60 | confirm_tx(crowdsale.transact({"from": deploy_address, "value": to_wei("0.01", "ether")}).buy()) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = ICOsmartcontracts 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends '!layout.html' %} 2 | 3 | {%- block footer %} 4 | 7 | 8 | {% if theme_github_banner|lower != 'false' %} 9 | 10 | Fork me on GitHub 11 | 12 | {% endif %} 13 | 14 | {% if theme_analytics_id %} 15 | 30 | {% endif %} 31 | {%- endblock %} 32 | 33 | -------------------------------------------------------------------------------- /docs/source/chain.rst: -------------------------------------------------------------------------------- 1 | .. _chain-configuration: 2 | 3 | =================== 4 | Chain configuration 5 | =================== 6 | 7 | Introduction 8 | ============ 9 | 10 | *ico* package uses underlying Populus framework to configure different Ethereum backends. 11 | 12 | Supported backend and nodes include 13 | 14 | * Go Ethereum (geth) 15 | 16 | * Parity 17 | 18 | * Ethereum mainnet 19 | 20 | * Ethereum Ropsten test network 21 | 22 | * Ethreum Kovan test network 23 | 24 | * ... or basically anything that responds to JSON RPC 25 | 26 | Default configuration 27 | ===================== 28 | 29 | The default configuration set in the packge distribution is in ``populus.json`` file. It is as 30 | 31 | * ``http://127.0.0.1:8545`` is mainnet JSON-RPC, `populus.json` network sa `mainnet` 32 | 33 | * ``http://127.0.0.1:8546`` is Kovan JSON-RPC, `populus.json` network sa `kovan` 34 | 35 | * ``http://127.0.0.1:8547`` is Kovan JSON-RPC, `populus.json` network sa `ropsten` 36 | 37 | Ethereum node software (geth, parity) must be started beforehand and configured to allow JSON-RPC in the particular port. 38 | 39 | Unlocking the deployment account 40 | ================================ 41 | 42 | For Parity you need to have `parity --unlock` given from the command line to unlock the account for automatic access. 43 | 44 | For Go Ethereum you need to use `geth console` and run `personal.unlockAccount` to unlock your account for some time, say 3600 seconds, before running scripts. 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # ICO smart contracts documentation build configuration file, created by 5 | # sphinx-quickstart on Thu Mar 30 22:42:13 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = 'ICO smart contracts' 51 | copyright = '2017, TokenMarket Ltd.' 52 | author = 'Mikko Ohtamaa' 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = '0.1' 60 | # The full version, including alpha/beta/rc tags. 61 | release = '0.1' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = [] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = 'sphinx' 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ---------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = 'alabaster' 88 | 89 | # Theme options are theme-specific and customize the look and feel of a theme 90 | # further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ['_static'] 99 | 100 | 101 | # -- Options for HTMLHelp output ------------------------------------------ 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = 'ICOsmartcontractsdoc' 105 | 106 | 107 | # -- Options for LaTeX output --------------------------------------------- 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | 118 | # Additional stuff for the LaTeX preamble. 119 | # 120 | # 'preamble': '', 121 | 122 | # Latex figure (float) alignment 123 | # 124 | # 'figure_align': 'htbp', 125 | } 126 | 127 | # Grouping the document tree into LaTeX files. List of tuples 128 | # (source start file, target name, title, 129 | # author, documentclass [howto, manual, or own class]). 130 | latex_documents = [ 131 | (master_doc, 'ICOsmartcontracts.tex', 'ICO smart contracts Documentation', 132 | 'Mikko Ohtamaa', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output --------------------------------------- 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, 'icosmartcontracts', 'ICO smart contracts Documentation', 142 | [author], 1) 143 | ] 144 | 145 | 146 | # -- Options for Texinfo output ------------------------------------------- 147 | 148 | # Grouping the document tree into Texinfo files. List of tuples 149 | # (source start file, target name, title, author, 150 | # dir menu entry, description, category) 151 | texinfo_documents = [ 152 | (master_doc, 'ICOsmartcontracts', 'ICO smart contracts Documentation', 153 | author, 'ICOsmartcontracts', 'One line description of project.', 154 | 'Miscellaneous'), 155 | ] 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/source/contracts.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Contracts 3 | ========= 4 | 5 | .. contents:: :local: 6 | 7 | Introduction 8 | ============ 9 | 10 | This chapter describers Ethereum crowdsale smart contracts. 11 | 12 | Preface 13 | ======= 14 | 15 | * You must understand Ethereum blockchain and `Solidity smart contract programming `_ basics 16 | 17 | * You must have a running Ethereum full node with JSON-RPC interface enabld 18 | 19 | TODO 20 | ==== 21 | -------------------------------------------------------------------------------- /docs/source/designchoices.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Design choices 3 | ============== 4 | 5 | .. contents:: :local: 6 | 7 | Introduction 8 | ============ 9 | 10 | In this chapter we explain some design choices made in the smart contracts. 11 | 12 | Timestamp vs. block number 13 | ========================== 14 | 15 | The code uses block timestamps instead of block numbers for start and events. We work on the assumption that crowdsale periods are not so short or time sensitive there would be need for block number based timing. Furthermore if the network miners start to skew block timestamps we might have a larger problem with dishonest miners. 16 | 17 | Crowdsale strategies and compound design pattern 18 | ================================================ 19 | 20 | Instead of cramming all the logic into a single contract through mixins and inheritance, we assemble our crowdsale from multiple components. Benefits include more elegant code, better reusability, separation of concern and testability. 21 | 22 | Mainly, our crowdsales have the following major parts 23 | 24 | * Crowdsale core: capped or uncapped 25 | 26 | * Pricing strategy: how price changes during the crowdsale 27 | 28 | * Finalizing strategy: What happens after a successful crowdsale: allow tokens to be transferable, give out extra tokens, etc. 29 | 30 | Background information 31 | ====================== 32 | 33 | * https://drive.google.com/file/d/0ByMtMw2hul0EN3NCaVFHSFdxRzA/view 34 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | ICO smart contract and tool documentation 3 | ========================================= 4 | 5 | 6 | This is a documentation for `ICO package `_ providing Ethereum smart contracts and Python based command line tools for launching your ICO crowdsale or token offering. 7 | 8 | `ICO stands for a token or cryptocurrency initial offering crowdsale `_. It is a common method in blockchain space, decentralized applications and in-game tokens for bootstrap funding of your project. 9 | 10 | This project aims to provide standard, secure smart contracts and tools to create crowdsales for Ethereum blockchain. 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | :caption: Contents: 15 | 16 | intro 17 | contracts 18 | install 19 | commands 20 | interact 21 | verification 22 | test 23 | chain 24 | designchoices 25 | other 26 | support 27 | 28 | .. image:: screenshots/walkthrough.png 29 | 30 | Links 31 | ===== 32 | 33 | `Github issue tracker and source code `_ 34 | 35 | `Documentation `_ 36 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | .. contents:: :local: 5 | 6 | Preface 7 | ^^^^^^^ 8 | 9 | Instructions are written in OSX and Linux in mind. 10 | 11 | Experience needed 12 | 13 | * Basic command line usage 14 | 15 | * Basic Github usage 16 | 17 | Setting up - OSX 18 | ^^^^^^^^^^^^^^^^ 19 | 20 | Packages needed 21 | 22 | * `Populus native dependencies `_ 23 | 24 | `Get Solidity compiler `_. Use version 0.4.12+. For OSX: 25 | 26 | .. code-block:: console 27 | 28 | brew install solidity 29 | 30 | Clone this repository from Github using submodules:: 31 | 32 | git clone --recursive git@github.com:TokenMarketNet/ico.git 33 | 34 | Python 3.5+ required. `See installing Python `_. 35 | 36 | .. code-block:: console 37 | 38 | python3.5 --version 39 | Python 3.5.2 40 | 41 | Create virtualenv for Python package management in the project root folder (same as where ``setup.py`` is): 42 | 43 | .. code-block:: console 44 | 45 | python3.5 -m venv venv 46 | source venv/bin/activate 47 | pip install -r requirements.txt 48 | pip install -e . 49 | 50 | Setting up - Ubuntu Linux 16.04 51 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 52 | 53 | Install dependencies: 54 | 55 | .. code-block:: console 56 | 57 | sudo apt install -y git build-essential libssl-dev python3 python3-venv python3-setuptools python3-dev cmake libboost-all-dev 58 | 59 | Python 3.5+ required. Make sure you have a compatible version: 60 | 61 | .. code-block:: console 62 | 63 | python3.5 --version 64 | Python 3.5.2 65 | 66 | `Install Solidity solc compiler `_: 67 | 68 | .. code-block:: console 69 | 70 | sudo apt install software-properties-common 71 | sudo add-apt-repository -y ppa:ethereum/ethereum 72 | sudo apt update 73 | sudo apt install -y ethereum solc 74 | 75 | Then install ``ico`` Python package and its dependencies: 76 | 77 | .. code-block:: console 78 | 79 | git clone # ... 80 | cd Smart-Contracts 81 | python3.5 -m venv venv 82 | source venv/bin/activate 83 | pip install wheel 84 | pip install -r requirements.txt 85 | pip install -e . 86 | 87 | Using your desired Solc version 88 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 89 | Sometimes it's useful to use some certain version of the Solidity compiler, 90 | this can be done using py-solc package, like this: 91 | 92 | .. code-block:: console 93 | python -m solc.install v0.4.16 94 | 95 | If you are lucky, you can now run binary ~/.py-solc/solc-v0.4.16/bin/solc. 96 | The binary is not available every platform. 97 | Remember to update your PATH accordingly: 98 | 99 | .. code-block:: console 100 | export PATH=/home/YOURNAME/.py-solc/solc-v0.4.16/bin:$PATH 101 | -------------------------------------------------------------------------------- /docs/source/intro.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Introduction 3 | ============ 4 | 5 | .. contents:: :local: 6 | 7 | .. include:: ../../README.rst 8 | 9 | -------------------------------------------------------------------------------- /docs/source/other.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Other 3 | ===== 4 | 5 | .. contents:: :local: 6 | 7 | Importing raw keys 8 | ================== 9 | 10 | You often need need to work with raw private keys. To import a raw private key to geth you can do from console:: 11 | 12 | web3.personal.importRawKey("","") 13 | 14 | Private key must be **without** 0x prefixed hex format. 15 | 16 | More information 17 | 18 | * http://ethereum.stackexchange.com/a/10020/620 19 | -------------------------------------------------------------------------------- /docs/source/screenshots/etherscan_token_transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitRewards/IcoSmartContract/ef1b700e1de99e86a6da4dfe4a92583751c73142/docs/source/screenshots/etherscan_token_transfer.png -------------------------------------------------------------------------------- /docs/source/screenshots/etherscan_verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitRewards/IcoSmartContract/ef1b700e1de99e86a6da4dfe4a92583751c73142/docs/source/screenshots/etherscan_verify.png -------------------------------------------------------------------------------- /docs/source/screenshots/ipython.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitRewards/IcoSmartContract/ef1b700e1de99e86a6da4dfe4a92583751c73142/docs/source/screenshots/ipython.png -------------------------------------------------------------------------------- /docs/source/screenshots/myetherwallet_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitRewards/IcoSmartContract/ef1b700e1de99e86a6da4dfe4a92583751c73142/docs/source/screenshots/myetherwallet_token.png -------------------------------------------------------------------------------- /docs/source/screenshots/presale_invest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitRewards/IcoSmartContract/ef1b700e1de99e86a6da4dfe4a92583751c73142/docs/source/screenshots/presale_invest.png -------------------------------------------------------------------------------- /docs/source/screenshots/walkthrough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitRewards/IcoSmartContract/ef1b700e1de99e86a6da4dfe4a92583751c73142/docs/source/screenshots/walkthrough.png -------------------------------------------------------------------------------- /docs/source/support.rst: -------------------------------------------------------------------------------- 1 | Commercial support 2 | ================== 3 | 4 | Contact `TokenMarket for launching your ICO or crowdsale `_ 5 | -------------------------------------------------------------------------------- /docs/source/test.rst: -------------------------------------------------------------------------------- 1 | Test suite 2 | ========== 3 | 4 | .. contents:: :local: 5 | 6 | Introduction 7 | ^^^^^^^^^^^^ 8 | 9 | ICO package comes with extensive automated test suite for smart contracts. 10 | 11 | About Populus 12 | ^^^^^^^^^^^^^ 13 | 14 | `Populus `_ is a tool for the Ethereum blockchain and smart contract management. The project uses Populus internally. Populus is a Python based suite for 15 | 16 | * Running arbitrary Ethereum chains (mainnet, testnet, private testnet) 17 | 18 | * Running test suites against Solidity smart contracts 19 | 20 | 21 | Running tests 22 | ^^^^^^^^^^^^^ 23 | 24 | Running tests:: 25 | 26 | py.test tests 27 | 28 | Run a specific test:: 29 | 30 | py.test tests -k test_get_price_tiers 31 | -------------------------------------------------------------------------------- /docs/source/verification.rst: -------------------------------------------------------------------------------- 1 | .. _contract-verification: 2 | 3 | ================================= 4 | Contract source code verification 5 | ================================= 6 | 7 | .. contents:: :local: 8 | 9 | Verifying contracts on EtherScan 10 | ================================ 11 | 12 | ICO package has a semi-automated process to verify deployed contracts on `EtherScan verification service `_. 13 | 14 | Benefits of verification 15 | ======================== 16 | 17 | * You can see the state of your contract variables real time on EtherScan block explorer 18 | 19 | * You prove that there are deterministic and verifiable builds for your deployed smart contracts 20 | 21 | Prerequisites 22 | ============= 23 | 24 | * You need to have Chrome and `chromedriver `_ installed for the browser automation 25 | 26 | * You need to have `Splinter `_ Python package installed: 27 | 28 | .. code-block:: shell 29 | 30 | pip install Splinter 31 | 32 | How automatic verification works 33 | ================================ 34 | 35 | You need to specify the verification settings in your YAML deployment script for :ref:`deploy-contracts` command. 36 | 37 | You need to make sure that you have your Solidity version and optimization parameters correctly. 38 | 39 | Example how to get Solidity version: 40 | 41 | .. code-block:: shell 42 | 43 | solc --version 44 | 45 | Here is an example YAML section: 46 | 47 | .. code-block:: yaml 48 | 49 | # Use automated Chrome to verify all contracts on etherscan.io 50 | verify_on_etherscan: yes 51 | browser_driver: chrome 52 | 53 | solc: 54 | 55 | # This is the Solidity version tag we verify on EtherScan. 56 | # For available versions see 57 | # https://kovan.etherscan.io/verifyContract2 58 | # 59 | # See values in Compiler drop down. 60 | # You can also get the local compiler version with: 61 | # 62 | # solc --version 63 | # 64 | # Note that for EtherScan you need to add letter "v" at the front of the version 65 | # 66 | # Note: You need to have correct optmization settings for the compiler 67 | # in populus.json that matches what EtherScan is expecting. 68 | # 69 | version: v0.4.14+commit.c2215d46 70 | 71 | # 72 | # We supply these to EtherScan as the solc settings we used to compile the contract. 73 | # They must match values in populus.json compilication / backends section. 74 | # These are the defaults supplied with the default populus.json. 75 | # 76 | optimizations: 77 | optimizer: true 78 | runs: 500 79 | 80 | When you run `deploy-contracts` and `verify_on_etherscan` is turned `on`, a Chrome browser will automatically open after a contract has been deployed. It goes to Verify page on EtherScan and automatically submits all verification information, including libraries. 81 | 82 | In the case there is a problem with the verification, `deploy-contracts` will stop and ask you to continue. During this time, you can check what is the actual error from EtherScan on the opened Chrome browser. 83 | 84 | 85 | -------------------------------------------------------------------------------- /ico/cmd/deploycontracts.py: -------------------------------------------------------------------------------- 1 | """Deploy crowdsale and all related contracts contract.""" 2 | 3 | import click 4 | from populus import Project 5 | 6 | from ico.deploy import deploy_crowdsale_from_file 7 | 8 | 9 | @click.command() 10 | @click.option('--deployment-name', nargs=1, default="mainnet", help='YAML section name we are deploying. Usual options include "mainnet" or "kovan"', required=True) 11 | @click.option('--deployment-file', nargs=1, help='YAML file definiting the crowdsale', required=True) 12 | @click.option('--address', nargs=1, help='Deployment address that pays the gas for the deployment cost. This account must exist on Ethereum node you are connected to.', required=True) 13 | def main(deployment_file, deployment_name, address): 14 | """Makes a scripted multiple contracts deployed based on a YAML file. 15 | 16 | Reads the chain configuration information from populus.json. 17 | The resulting deployed contracts can be automatically verified on etherscan.io. 18 | 19 | Example: 20 | 21 | deploy-contracts --deployment-file=crowdsales/example.yml --deployment-name=kovan--address=0x001fc7d7e506866aeab82c11da515e9dd6d02c25 22 | 23 | Example files: 24 | 25 | * https://github.com/TokenMarketNet/ico/blob/master/crowdsales/allocated-token-sale-example.yml 26 | 27 | * https://github.com/TokenMarketNet/ico/blob/master/crowdsales/example.yml 28 | """ 29 | 30 | project = Project() 31 | deploy_crowdsale_from_file(project, deployment_file, deployment_name, address) 32 | print("All done! Enjoy your decentralized future.") 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /ico/cmd/investors.py: -------------------------------------------------------------------------------- 1 | """Extract crowdsale investor data.""" 2 | import csv 3 | import datetime 4 | from collections import OrderedDict 5 | 6 | import click 7 | from eth_utils import from_wei 8 | from populus import Project 9 | 10 | 11 | @click.command() 12 | @click.option('--chain', nargs=1, default="mainnet", help='On which chain to deploy - see populus.json') 13 | @click.option('--address', nargs=1, help='CrowdsaleContract address to scan', required=True) 14 | @click.option('--csv-file', nargs=1, help='CSV fil to write', default=None) 15 | def main(chain, address, csv_file): 16 | """Extract crowdsale contract investors.""" 17 | 18 | project = Project() 19 | 20 | with project.get_chain(chain) as c: 21 | 22 | web3 = c.web3 23 | print("Web3 provider is", web3.currentProvider) 24 | # Sanity check 25 | print("Block number is", web3.eth.blockNumber) 26 | 27 | Crowdsale = c.provider.get_contract_factory('MintedTokenCappedCrowdsale') 28 | crowdsale = Crowdsale(address=address) 29 | 30 | print("Total amount raised is", from_wei(crowdsale.call().weiRaised(), "ether"), "ether") 31 | 32 | print("Getting events") 33 | events = crowdsale.pastEvents("Invested").get(only_changes=False) 34 | 35 | # Merge several transactions from the same address to one 36 | print("Analysing", len(events), "raw events") 37 | address_data = OrderedDict() 38 | for e in events: 39 | address = e["args"]["investor"] 40 | data = address_data.get(address, {}) 41 | 42 | # TODO: Not sure if we get events in block order 43 | timestamp = web3.eth.getBlock(e["blockNumber"])["timestamp"] 44 | current_first = data.get("first_payment", 99999999999999999) 45 | if timestamp < current_first: 46 | data["first_payment"] = timestamp 47 | 48 | data["raised"] = data.get("raised", 0) + from_wei(e["args"]["weiAmount"], "ether") 49 | data["tokens"] = data.get("tokens", 0) + e["args"]["tokenAmount"] 50 | address_data[address] = data 51 | 52 | if csv_file: 53 | print("Writing results to", csv_file) 54 | with open(csv_file, 'w', newline='') as out: 55 | writer = csv.writer(out) 56 | 57 | writer.writerow(["Address", "First payment at", "Invested ETH", "Received tokens"]) 58 | 59 | for address, data in address_data.items(): 60 | timestamp = data["first_payment"] 61 | dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) 62 | writer.writerow([ 63 | address, 64 | dt.isoformat(), 65 | str(data["raised"]), 66 | str(data["tokens"]) 67 | ]) 68 | else: 69 | for address, data in address_data.items(): 70 | timestamp = data["first_payment"] 71 | dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) 72 | print( 73 | address, 74 | dt.isoformat(), 75 | str(data["raised"]), 76 | str(data["tokens"]) 77 | ) 78 | 79 | print("Total", len(address_data), "investors") 80 | print("All done! Enjoy your decentralized future.") 81 | 82 | 83 | if __name__ == "__main__": 84 | main() 85 | -------------------------------------------------------------------------------- /ico/cmd/rawinvestments.py: -------------------------------------------------------------------------------- 1 | """Extract crowdsale raw investmetn data.""" 2 | import csv 3 | import datetime 4 | import json 5 | import os 6 | 7 | import click 8 | from decimal import Decimal 9 | from eth_utils import from_wei 10 | from populus import Project 11 | 12 | 13 | @click.command() 14 | @click.option('--chain', nargs=1, default="mainnet", help='On which chain to deploy - see populus.json') 15 | @click.option('--address', nargs=1, help='CrowdsaleContract address to scan', required=True) 16 | @click.option('--csv-file', nargs=1, help='CSV file to write', default=None, required=True) 17 | def main(chain, address, csv_file): 18 | """Extract crowdsale invested events. 19 | 20 | This is useful for RelaunchCrowdsale to rebuild the data. 21 | """ 22 | 23 | project = Project() 24 | timestamp_filename = "block-timestamps.json" 25 | 26 | with project.get_chain(chain) as c: 27 | 28 | web3 = c.web3 29 | print("Web3 provider is", web3.currentProvider) 30 | # Sanity check 31 | print("Block number is", web3.eth.blockNumber) 32 | 33 | Crowdsale = c.provider.get_base_contract_factory('MintedTokenCappedCrowdsale') 34 | crowdsale = Crowdsale(address=address) 35 | 36 | Token = c.provider.get_base_contract_factory('CrowdsaleToken') 37 | token = Token(address=crowdsale.call().token()) 38 | 39 | decimals = token.call().decimals() 40 | decimal_multiplier = 10**decimals 41 | 42 | print("We have", decimals, "decimals, multiplier is", decimal_multiplier) 43 | 44 | print("Total amount raised is", from_wei(crowdsale.call().weiRaised(), "ether"), "ether") 45 | 46 | print("Getting events") 47 | events = crowdsale.pastEvents("Invested").get(only_changes=False) 48 | 49 | print("Writing results to", csv_file) 50 | 51 | # Block number -> timestamp mappings 52 | timestamps = {} 53 | 54 | # Load cached timestamps 55 | if os.path.exists(timestamp_filename): 56 | with open(timestamp_filename, "rt") as inp: 57 | timestamps = json.load(inp) 58 | 59 | with open(csv_file, 'w', newline='') as out: 60 | writer = csv.writer(out) 61 | 62 | writer.writerow(["Address", "Payment at", "Tx hash", "Tx index", "Invested ETH", "Received tokens"]) 63 | 64 | for idx, e in enumerate(events): 65 | 66 | if idx % 100 == 0: 67 | print("Writing event", idx) 68 | # Save cached timestamps 69 | with open(timestamp_filename, "wt") as out: 70 | json.dump(timestamps, out) 71 | 72 | block_number = e["blockNumber"] 73 | if block_number not in timestamps: 74 | timestamps[block_number] = web3.eth.getBlock(block_number)["timestamp"] 75 | 76 | amount = Decimal(e["args"]["tokenAmount"]) / Decimal(decimal_multiplier) 77 | 78 | tokens = amount * decimal_multiplier 79 | 80 | # http://stackoverflow.com/a/19965088/315168 81 | if not tokens % 1 == 0: 82 | raise RuntimeError("Could not convert token amount to decimal format. It was not an integer after restoring non-fractional balance: {} {} {}".format(tokens, amount, decimal_multiplier)) 83 | 84 | timestamp = timestamps[block_number] 85 | dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) 86 | writer.writerow([ 87 | e["args"]["investor"], 88 | dt.isoformat(), 89 | e["transactionHash"], 90 | e["transactionIndex"], 91 | from_wei(e["args"]["weiAmount"], "ether"), 92 | amount, 93 | ]) 94 | 95 | print("Total", len(events), "invest events") 96 | print("All done! Enjoy your decentralized future.") 97 | 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /ico/definition.py: -------------------------------------------------------------------------------- 1 | """Process YAML crowdsale definition files.""" 2 | import datetime 3 | import time 4 | from collections import OrderedDict 5 | from typing import Dict 6 | 7 | import ruamel.yaml 8 | import jinja2 9 | 10 | from eth_utils.currency import to_wei 11 | from ruamel.yaml.comments import CommentedMap 12 | from web3 import Web3 13 | from web3.contract import Contract 14 | 15 | from ico.state import CrowdsaleState 16 | from ico.utils import check_succesful_tx 17 | from ico.utils import check_multiple_succesful_txs 18 | 19 | 20 | def _datetime(*args) -> datetime.datetime: 21 | """Construct UTC datetime.""" 22 | return datetime.datetime(*args, tzinfo=datetime.timezone.utc) 23 | 24 | 25 | def _timestamp(dt) -> int: 26 | """Convert UTC datetime to unix timestamp.""" 27 | epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) 28 | t = (dt - epoch).total_seconds() 29 | return int(t) 30 | 31 | 32 | def _time() -> int: 33 | """Current UNIX timestamp.""" 34 | return int(time.time()) 35 | 36 | 37 | def load_investor_data(contract: Contract, deploy_address: str, fname: str): 38 | """Load investor data to a MultiVault contract. 39 | 40 | Mysterium specific data loader. 41 | 42 | :return: List of unconfirmed transaction ids 43 | """ 44 | 45 | assert fname.endswith(".csv") 46 | 47 | txs = [] 48 | with open(fname, "rt") as inp: 49 | for line in inp: 50 | address, amount = line.split(",") 51 | address = address.strip() 52 | amount = amount.strip() 53 | assert address.startswith("0x") 54 | amount = int(float(amount) * 10000) # Use this precision 55 | txs.append(contract.transact({"from": deploy_address}).addInvestor(address, amount)) 56 | 57 | return txs 58 | 59 | 60 | def extract_deployment_details(yaml_filename: str, chain: str) -> dict: 61 | """Read yaml definition file and interpolate all variables.""" 62 | with open(yaml_filename, "rt") as inp: 63 | data = ruamel.yaml.load(inp, ruamel.yaml.RoundTripLoader) 64 | return data[chain] 65 | 66 | 67 | def get_jinja_context(data: dict) -> dict: 68 | """Create Jinja template variables and functions""" 69 | 70 | # Define helper functions 71 | context = { 72 | "time": _time, 73 | "timestamp": _timestamp, 74 | "datetime": _datetime, 75 | "to_wei": to_wei, 76 | } 77 | 78 | # Copy run-time data to template context 79 | for key, value in data.items(): 80 | context[key] = value 81 | 82 | return context 83 | 84 | 85 | def get_post_actions_context(section_data: str, runtime_data: dict, contracts: Dict[str, Contract], web3: Web3) -> dict: 86 | """Get Python evalution context for post-deploy and verify actions. 87 | 88 | :param runtime_data: 89 | :param contracts: Dictionary of deployed contract objects 90 | :param section: "post_actions" or "verify" 91 | :return: 92 | """ 93 | 94 | context = get_jinja_context(runtime_data) 95 | 96 | def _confirm_tx(txid): 97 | check_succesful_tx(web3, txid) 98 | 99 | def _confirm_multiple_txs(*txids, timeout=180): 100 | check_multiple_succesful_txs(web3, txids, timeout=timeout) 101 | 102 | # Make contracts available in the context 103 | for name, contract in contracts.items(): 104 | context[name] = contract 105 | 106 | context["CrowdsaleState"] = CrowdsaleState 107 | context["confirm_tx"] = _confirm_tx 108 | context["confirm_multiple_txs"] = _confirm_multiple_txs 109 | context["load_investor_data"] = load_investor_data 110 | return context 111 | 112 | 113 | def interpolate_value(value: str, context: dict): 114 | """Expand Jinja templating in the definitions.""" 115 | 116 | if type(value) == str and "{{" in value: 117 | t = jinja2.Template(value, undefined=jinja2.StrictUndefined) 118 | try: 119 | v = t.render(**context) 120 | except jinja2.exceptions.TemplateError as e: 121 | raise RuntimeError("Could not expand template value: {}".format(value)) from e 122 | 123 | # Jinja template rendering does not have explicit int support, 124 | # so we have this hack in place 125 | try: 126 | v = int(v) 127 | except ValueError: 128 | pass 129 | 130 | return v 131 | else: 132 | return value 133 | 134 | 135 | def interpolate_data(data: dict, context: dict) -> dict: 136 | new = OrderedDict() 137 | for k, v in data.items(): 138 | if isinstance(v, (dict, CommentedMap)): 139 | v = interpolate_data(v, context) 140 | elif isinstance(v, list): 141 | v = [interpolate_value(item , context) for item in v] 142 | else: 143 | v = interpolate_value(v, context) 144 | 145 | new[k] = v 146 | return new 147 | 148 | 149 | def load_crowdsale_definitions(yaml_filename, chain: str): 150 | """Load crowdsale definitions from YAML file and replace all values.""" 151 | data = extract_deployment_details(yaml_filename, chain) 152 | return data 153 | 154 | -------------------------------------------------------------------------------- /ico/earlypresale.py: -------------------------------------------------------------------------------- 1 | """Tools for moving funds from a presale contract to ICO contract early.""" 2 | import logging 3 | 4 | from eth_utils import from_wei 5 | from web3 import Web3 6 | 7 | from ico.utils import check_succesful_tx 8 | from ico.utils import get_contract_by_name 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | def participate_early(chain, web3: Web3, presale_address: str, crowdsale_address: str, deploy_address: str, start=0, end=32, timeout=300) -> int: 15 | """Move funds over early. 16 | 17 | .. note :: 18 | 19 | Crowdsale contract checks the participate whitelist by invest address, not by msg.sender. 20 | This process will open the presale investors an ability to participate to the crowdsale early, 21 | bypassing the retail investor start time. However they could also top up their existing 22 | preico accounts, so this is largerly no issue. 23 | 24 | 25 | :param start: Move only n investors (for testing purposes) 26 | :param end: Move only n investors (for testing purposes) 27 | """ 28 | 29 | updated = 0 30 | 31 | PresaleFundCollector = get_contract_by_name(chain, "PresaleFundCollector") 32 | presale = PresaleFundCollector(address=presale_address) 33 | 34 | Crowdsale = PresaleFundCollector = get_contract_by_name(chain, "Crowdsale") 35 | crowdsale = Crowdsale(address=crowdsale_address) 36 | 37 | # Make sure presale is correctly set 38 | txid = presale.transact({"from": deploy_address}).setCrowdsale(crowdsale.address) 39 | logger.info("Setting presale crowdsale address to %s on txid", crowdsale.address, txid) 40 | check_succesful_tx(web3, txid, timeout=timeout) 41 | 42 | # Double check presale has a presale price set 43 | MilestonePricing = get_contract_by_name(chain, "MilestonePricing") 44 | pricing_strategy = MilestonePricing(address=crowdsale.call().pricingStrategy()) 45 | 46 | if not pricing_strategy.call().preicoAddresses(presale.address): 47 | raise RuntimeError("Was not listed as presale address for pricing: {}".format(presale.address)) 48 | 49 | for i in range(start, min(end, presale.call().investorCount())): 50 | 51 | investor = presale.call().investors(i) 52 | 53 | if presale.call().balances(investor) > 0: 54 | print("Whitelisting for {} to crowdsale {}".format(investor, crowdsale.address)) 55 | txid = crowdsale.transact({"from": deploy_address}).setEarlyParicipantWhitelist(investor, True) 56 | print("Broadcasting whitelist transaction {}".format(txid)) 57 | check_succesful_tx(web3, txid, timeout=timeout) 58 | 59 | funds = from_wei(presale.call().balances(investor), "ether") 60 | print("Moving funds {} ETH for investor {} to presale {}".format(funds, investor, presale.address)) 61 | txid = presale.transact({"from": deploy_address}).participateCrowdsaleInvestor(investor) 62 | print("Broadcasting transaction {}".format(txid)) 63 | check_succesful_tx(web3, txid, timeout=timeout) 64 | updated += 1 65 | else: 66 | print("Investor already handled: {}".format(investor)) 67 | 68 | return updated 69 | 70 | -------------------------------------------------------------------------------- /ico/etherscan.py: -------------------------------------------------------------------------------- 1 | """etherscan.io contract verification.""" 2 | 3 | import time 4 | 5 | from populus import Project 6 | 7 | from ico.importexpand import expand_contract_imports 8 | 9 | 10 | def _fill_in_textarea_value(browser, splinter_elem, value): 11 | """Do not do sendKeys() for large text. 12 | 13 | https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/4469#issuecomment-192090165 14 | """ 15 | elem = splinter_elem._element 16 | browser.driver.execute_script(''' 17 | var elem = arguments[0]; 18 | var value = arguments[1]; 19 | elem.value = value; 20 | ''', elem, value) 21 | 22 | 23 | 24 | def verify_contract(project: Project, chain_name: str, address: str, contract_name, contract_filename: str, constructor_args: str, libraries: dict, optimization=True, optimizer_runs=200, compiler: str="v0.4.8+commit.60cc1668", browser_driver="chrome") -> str: 25 | """Semi-automated contract verified on Etherscan. 26 | 27 | Uses a web browser + Selenium auto fill to verify contracts. 28 | 29 | See the page in action: https://etherscan.io/verifyContract2 30 | 31 | :return: Contract expanded source code 32 | """ 33 | 34 | try: 35 | from splinter import Browser 36 | except ImportError: 37 | raise RuntimeError("Splinter package must be installed for verification automation") 38 | 39 | src, imported_files = expand_contract_imports(project, contract_filename) 40 | 41 | if chain_name == "mainnet": 42 | url = "https://etherscan.io/verifyContract2" 43 | elif chain_name == "rinkeby": 44 | url = "https://rinkeby.etherscan.io/verifyContract2" 45 | elif chain_name == "ropsten": 46 | url = "https://ropsten.etherscan.io/verifyContract2" 47 | elif chain_name == "kovan": 48 | url = "https://kovan.etherscan.io/verifyContract2" 49 | else: 50 | raise RuntimeError("Unknown chain: ".format(chain_name)) 51 | 52 | # Etherscan does not want 0x prefix 53 | if constructor_args.startswith("0x"): 54 | constructor_args = constructor_args[2:] 55 | 56 | with Browser(driver_name=browser_driver) as browser: 57 | browser.visit(url) 58 | 59 | browser.fill("ctl00$ContentPlaceHolder1$txtContractAddress", address) 60 | browser.fill("ctl00$ContentPlaceHolder1$txtContractName", contract_name) 61 | browser.select("ctl00$ContentPlaceHolder1$ddlCompilerVersions", compiler) 62 | browser.select("ctl00$ContentPlaceHolder1$ddlOptimization", "1" if optimization else "0") 63 | browser.fill("ctl00$ContentPlaceHolder1$txtRuns", optimizer_runs) 64 | _fill_in_textarea_value(browser, browser.find_by_name("ctl00$ContentPlaceHolder1$txtSourceCode"), src) 65 | _fill_in_textarea_value(browser, browser.find_by_name("ctl00$ContentPlaceHolder1$txtConstructorArguements"), constructor_args) 66 | 67 | idx = 1 68 | for library_name, library_address in libraries.items(): 69 | browser.fill("ctl00$ContentPlaceHolder1$txtLibraryAddress{}".format(idx),library_address) 70 | browser.fill("ctl00$ContentPlaceHolder1$txtLibraryName{}".format(idx), library_name) 71 | idx += 1 72 | 73 | # Give the contract deployment some time to propagate so that Etherscan will find it 74 | time.sleep(30) 75 | 76 | 77 | browser.find_by_name("ctl00$ContentPlaceHolder1$btnSubmit").click() 78 | 79 | deadline = time.time() + 60 80 | 81 | print("Waiting EtherScan to process the contract verification") 82 | while time.time() < deadline: 83 | if browser.is_text_present("Successfully generated ByteCode and ABI for Contract Address", wait_time=1): 84 | return src 85 | 86 | if browser.is_text_present("already been verified"): 87 | print("The contract has already been verified") 88 | return src 89 | 90 | time.sleep(1.0) 91 | 92 | print("Contract verification failed. Check the browser for details.") 93 | print("Make sure solc_version in your YAML file matches solc --version output.") 94 | print("Make sure solc optimization settings are the same as what EtherScan thinks.") 95 | input("Press enter to continue") 96 | 97 | return src 98 | 99 | 100 | def get_etherscan_link(network, address): 101 | """Construct etherscan link""" 102 | 103 | if network == "mainnet": 104 | return "https://etherscan.io/address/" + address 105 | elif network == "rinkeby": 106 | return "https://rinkeby.etherscan.io/address/" + address 107 | elif network == "ropsten": 108 | return "https://ropsten.etherscan.io/address/" + address 109 | elif network == "kovan": 110 | return "https://kovan.etherscan.io/address/" + address 111 | else: 112 | raise RuntimeError("Unknown network: {}".format(network)) 113 | -------------------------------------------------------------------------------- /ico/importexpand.py: -------------------------------------------------------------------------------- 1 | """Expand Solidity import statements for Etherscan verification service. 2 | 3 | Mainly need for EtherScan verification service. 4 | """ 5 | import os 6 | from typing import Tuple 7 | 8 | from populus import Project 9 | 10 | 11 | class Expander: 12 | """Solidity import expanded.""" 13 | 14 | def __init__(self, project: Project): 15 | self.project = project 16 | self.processed_imports = set() 17 | self.pragma_processed = False 18 | 19 | def expand_file(self, import_path: str, parent_path=None): 20 | """Read Solidity source code and expart any import paths inside. 21 | 22 | Supports Populus remapping settings: 23 | 24 | http://populus.readthedocs.io/en/latest/config.html#compiler-settings 25 | 26 | :param import_path: 27 | """ 28 | 29 | # TODO: properly handle import remapping here, read them from project config 30 | if import_path.startswith("zeppelin/"): 31 | abs_import_path = os.path.join(os.getcwd(), import_path) 32 | else: 33 | if not parent_path: 34 | abs_import_path = os.path.join(os.getcwd(), "contracts", import_path) 35 | else: 36 | abs_import_path = os.path.join(parent_path, import_path) 37 | 38 | abs_import_path = os.path.abspath(abs_import_path) 39 | 40 | # Already handled 41 | if abs_import_path in self.processed_imports: 42 | return "" 43 | 44 | print("Expanding source code file", import_path) 45 | 46 | current_path = os.path.dirname(abs_import_path) 47 | 48 | with open(abs_import_path, "rt") as inp: 49 | source = inp.read() 50 | self.processed_imports.add(abs_import_path) 51 | return self.process_source(source, current_path) 52 | 53 | def process_source(self, src: str, parent_path: str): 54 | """Process Solidity source code and expand any import statement.""" 55 | 56 | out = [] 57 | 58 | for line in src.split("\n"): 59 | # Detect import statements, ghetto way 60 | if line.startswith('import "'): 61 | prefix, import_path, suffix = line.split('"') 62 | source = self.expand_file(import_path, parent_path=parent_path) 63 | out += source.split("\n") 64 | elif line.startswith("import '"): 65 | prefix, import_path, suffix = line.split("'") 66 | source = self.expand_file(import_path, parent_path=parent_path) 67 | out += source.split("\n") 68 | elif line.startswith('pragma'): 69 | # Only allow one pragma statement per file 70 | if self.pragma_processed: 71 | continue 72 | else: 73 | self.pragma_processed = True 74 | else: 75 | out.append(line) 76 | 77 | return "\n".join(out) 78 | 79 | 80 | def expand_contract_imports(project: Project, contract_filename: str) -> Tuple[str, str]: 81 | """Expand Solidity import statements. 82 | 83 | :return: Tuple[final expanded source, set of processed filenames] 84 | """ 85 | exp = Expander(project) 86 | return exp.expand_file(contract_filename), exp.processed_imports 87 | -------------------------------------------------------------------------------- /ico/kyc.py: -------------------------------------------------------------------------------- 1 | """AML data passing helpers.""" 2 | from uuid import UUID 3 | 4 | from eth_utils import is_checksum_address 5 | 6 | 7 | def pack_kyc_dataframe(whitelisted_address: str, customer_id: UUID, min_eth_10k: int, max_eth_10k: int) -> bytes: 8 | """Pack KYC information to the smart contract. 9 | 10 | See KYCPayloadDeserializer for the matching Solidity code. 11 | 12 | :param whitelisted_address: Must be whitelisted address in a Ethereum checksummed format 13 | :param customer_id: Customer id as UUIDv8 14 | :param min_eth: Min investment for this customer. Expressed as the parts of 1/10000. 15 | :param max_eth: Max investment for this customer. Expressed as the parts of 1/10000. 16 | :return: 17 | """ 18 | assert is_checksum_address(whitelisted_address) 19 | assert isinstance(customer_id, UUID) 20 | assert type(min_eth_10k) == int 21 | assert type(max_eth_10k) == int 22 | addr_value = int(whitelisted_address, 16) 23 | addr_b = addr_value.to_bytes(20, byteorder="big") # Ethereum address is 20 bytes 24 | customer_b = customer_id.bytes 25 | min_b = min_eth_10k.to_bytes(4, byteorder="big") 26 | max_b = max_eth_10k.to_bytes(4, byteorder="big") 27 | data = addr_b + customer_b + min_b + max_b 28 | assert len(data) == 44, "Got length: {}".format(len(data)) 29 | return data 30 | -------------------------------------------------------------------------------- /ico/sign.py: -------------------------------------------------------------------------------- 1 | """Sign data with Ethereum private key.""" 2 | import binascii 3 | 4 | import bitcoin 5 | from eth_utils import pad_left 6 | from ethereum import utils 7 | from ethereum.utils import big_endian_to_int, sha3 8 | from secp256k1 import PrivateKey 9 | 10 | 11 | def get_ethereum_address_from_private_key(private_key_seed_ascii: str) -> str: 12 | """Generate Ethereum address from a private key. 13 | 14 | https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py#L111 15 | 16 | :param private_key: Any string as a seed 17 | 18 | :return: 0x prefixed hex string 19 | """ 20 | priv = utils.sha3(private_key_seed_ascii) 21 | pub = bitcoin.encode_pubkey(bitcoin.privtopub(priv), 'bin_electrum') 22 | return "0x" + binascii.hexlify(sha3(pub)[12:]).decode("ascii") 23 | 24 | 25 | def get_address_as_bytes(address: str) -> bytes: 26 | """Convert Ethereum address to byte data payload for signing.""" 27 | assert address.startswith("0x") 28 | address_bytes = binascii.unhexlify(address[2:]) 29 | return address_bytes 30 | 31 | 32 | def sign(data: bytes, private_key_seed_ascii: str, hash_function=bitcoin.bin_sha256): 33 | """Sign data using Ethereum private key. 34 | 35 | :param private_key_seed_ascii: Private key seed as ASCII string 36 | """ 37 | 38 | msghash = hash_function(data) 39 | 40 | priv = utils.sha3(private_key_seed_ascii) 41 | pub = bitcoin.privtopub(priv) 42 | 43 | # Based on ethereum/tesrt_contracts.py test_ecrecover 44 | pk = PrivateKey(priv, raw=True) 45 | 46 | signature = pk.ecdsa_recoverable_serialize(pk.ecdsa_sign_recoverable(msghash, raw=True)) 47 | signature = signature[0] + utils.bytearray_to_bytestr([signature[1]]) 48 | 49 | # Enforce non-tightly-packed arguments for signing 50 | # (0x00 left pad) 51 | # https://github.com/ethereum/web3.py/issues/466 52 | v = utils.safe_ord(signature[64]) + 27 53 | r_bytes = signature[0:32] 54 | r_bytes = pad_left(r_bytes, 32, b"\0") 55 | r = big_endian_to_int(r_bytes) 56 | s_bytes = signature[32:64] 57 | s_bytes = pad_left(s_bytes, 32, b"\0") 58 | s = big_endian_to_int(s_bytes) 59 | 60 | # Make sure we use bytes data and zero padding stays 61 | # good across different systems 62 | r_hex = binascii.hexlify(r_bytes).decode("ascii") 63 | s_hex = binascii.hexlify(s_bytes).decode("ascii") 64 | 65 | # Convert to Etheruem address format 66 | addr = utils.big_endian_to_int(utils.sha3(bitcoin.encode_pubkey(pub, 'bin')[1:])[12:]) 67 | 68 | # Return various bits about signing so it's easier to debug 69 | return { 70 | "signature": signature, 71 | "v": v, 72 | "r": r, 73 | "s": s, 74 | "r_bytes": r_bytes, 75 | "s_bytes": s_bytes, 76 | "r_hex": "0x" + r_hex, 77 | "s_hex": "0x" + s_hex, 78 | "address_bitcoin": addr, 79 | "address_ethereum": get_ethereum_address_from_private_key(priv), 80 | "public_key": pub, 81 | "hash": msghash, 82 | "payload": binascii.hexlify(bytes([v] + list(r_bytes)+ list(s_bytes,))) 83 | } 84 | 85 | 86 | def verify(msghash: bytes, signature, public_key): 87 | """Verify that data has been signed with Etheruem private key. 88 | :param signature: 89 | :return: 90 | """ 91 | 92 | V = utils.safe_ord(signature[64]) + 27 93 | R = big_endian_to_int(signature[0:32]) 94 | S = big_endian_to_int(signature[32:64]) 95 | 96 | return bitcoin.ecdsa_raw_verify(msghash, (V, R, S), public_key) 97 | 98 | -------------------------------------------------------------------------------- /ico/state.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class CrowdsaleState(IntEnum): 5 | """Match Crowdsale.State in the contract.""" 6 | Unknown = 0 7 | Preparing = 1 8 | PreFunding = 2 9 | Funding = 3 10 | Success = 4 11 | Failure = 5 12 | Finalized = 6 13 | Refunding = 7 14 | 15 | 16 | class UpgradeState(IntEnum): 17 | """Match UpgradeAgentEnabledToken.State in the contract.""" 18 | Unknown = 0 19 | NotAllowed = 1 20 | WaitingForAgent = 2 21 | ReadyToUpgrade = 3 22 | Upgrading = 4 23 | 24 | 25 | -------------------------------------------------------------------------------- /ico/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Test fixtures.""" 2 | 3 | 4 | import pytest 5 | from web3.contract import Contract 6 | 7 | 8 | def nice_list_pytest_itemcollected(item): 9 | """Visualize tests run so we can copy paste output. 10 | 11 | http://stackoverflow.com/q/28898919/315168 12 | 13 | TODO: Make env variable activated 14 | """ 15 | par = item.parent.obj 16 | node = item.obj 17 | pref = par.__doc__.strip() if par.__doc__ else par.__class__.__name__ 18 | suf = node.__doc__.strip() if node.__doc__ else node.__name__ 19 | if pref or suf: 20 | pref = pref.replace(".", ":") 21 | item._nodeid = ' '.join((pref, suf)) 22 | item._nodeid.rstrip(".") 23 | 24 | 25 | from ico.tests.fixtures.amltoken import * # noqa 26 | from ico.tests.fixtures.general import * # noqa 27 | from ico.tests.fixtures.flatprice import * # noqa 28 | from ico.tests.fixtures.releasable import * # noqa 29 | from ico.tests.fixtures.finalize import * # noqa 30 | from ico.tests.fixtures.presale import * # noqa 31 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_amltoken.py: -------------------------------------------------------------------------------- 1 | """Releasable AMLToken token.""" 2 | 3 | import pytest 4 | from ethereum.tester import TransactionFailed 5 | from web3.contract import Contract 6 | 7 | 8 | def test_transfer_to_owner(aml_token: Contract, team_multisig: str, malicious_address: str, empty_address: str): 9 | """Only owner can move tokens back to him before releasing.""" 10 | 11 | starting_amount = aml_token.call().balanceOf(team_multisig) 12 | 13 | aml_token.transact({"from": team_multisig}).setTransferAgent(team_multisig, True) 14 | aml_token.transact({"from": team_multisig}).transfer(malicious_address, 1000000) 15 | 16 | assert starting_amount != aml_token.call().balanceOf(team_multisig) 17 | 18 | aml_token.transact({"from": team_multisig}).transferToOwner(malicious_address) 19 | 20 | aml_token.transact({"from": team_multisig}).setReleaseAgent(team_multisig) 21 | aml_token.transact({"from": team_multisig}).releaseTokenTransfer() 22 | 23 | assert starting_amount == aml_token.call().balanceOf(team_multisig) 24 | 25 | 26 | def test_transfer_to_owner_after_release(aml_token: Contract, team_multisig: str, malicious_address: str, empty_address: str): 27 | """Only owner can move tokens back to him before releasing.""" 28 | 29 | aml_token.transact({"from": team_multisig}).setReleaseAgent(team_multisig) 30 | aml_token.transact({"from": team_multisig}).releaseTokenTransfer() 31 | 32 | aml_token.transact({"from": team_multisig}).transfer(malicious_address, 1000000) 33 | 34 | with pytest.raises(TransactionFailed): 35 | aml_token.transact({"from": team_multisig}).transferToOwner(malicious_address) 36 | 37 | 38 | def test_transfer_to_owner_only_owner(aml_token: Contract, team_multisig: str, malicious_address: str, empty_address: str): 39 | """Other parties cannot do AML reclaim.""" 40 | 41 | starting_amount = aml_token.call().balanceOf(team_multisig) 42 | 43 | aml_token.transact({"from": team_multisig}).setTransferAgent(team_multisig, True) 44 | aml_token.transact({"from": team_multisig}).transfer(malicious_address, 1000000) 45 | 46 | assert starting_amount != aml_token.call().balanceOf(team_multisig) 47 | 48 | with pytest.raises(TransactionFailed): 49 | aml_token.transact({"from": malicious_address}).transferToOwner(malicious_address) 50 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_approval.py: -------------------------------------------------------------------------------- 1 | """Approval race condition mitigation.""" 2 | """Commented out temporarily""" 3 | from web3.contract import Contract 4 | 5 | 6 | #def test_increase_approval(released_token: Contract, customer: str, empty_address: str, allowed_party): 7 | # """Increase approval.""" 8 | # 9 | # token = released_token 10 | # amount = 5000 11 | # change = 1000 12 | # assert token.call().allowance(customer, allowed_party) == 0 13 | # token.transact({"from": customer}).approve(allowed_party, amount) 14 | # token.transact({"from": customer}).addApproval(allowed_party, change) 15 | # assert token.call().allowance(customer, allowed_party) == amount + change 16 | 17 | 18 | #def test_decrease_approval(released_token: Contract, customer: str, empty_address: str, allowed_party): 19 | # """Decrease approval.""" 20 | 21 | # token = released_token 22 | # amount = 5000 23 | # change = 1000 24 | # assert token.call().allowance(customer, allowed_party) == 0 25 | # token.transact({"from": customer}).approve(allowed_party, amount) 26 | # token.transact({"from": customer}).subApproval(allowed_party, change) 27 | # assert token.call().allowance(customer, allowed_party) == amount - change 28 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_burn.py: -------------------------------------------------------------------------------- 1 | """Burn functionality.""" 2 | 3 | import datetime 4 | import pytest 5 | from ethereum.tester import TransactionFailed 6 | from web3.contract import Contract 7 | 8 | 9 | @pytest.fixture 10 | def token(chain, team_multisig): 11 | args = [ 12 | team_multisig, 13 | "Token", 14 | "TKN", 15 | 1000000, 16 | 0, 17 | int((datetime.datetime(2017, 4, 22, 16, 0) - datetime.datetime(1970, 1, 1)).total_seconds()) 18 | ] 19 | contract, hash = chain.provider.deploy_contract('CentrallyIssuedToken', deploy_args=args) 20 | assert contract.call().balanceOf(team_multisig) == 1000000 21 | 22 | contract.transact({"from": team_multisig}).releaseTokenTransfer() 23 | return contract 24 | 25 | 26 | @pytest.fixture 27 | def token_with_customer_balance(chain, team_multisig, token, customer) -> Contract: 28 | """Create a Crowdsale token where transfer restrictions have been lifted.""" 29 | 30 | # Make sure customer 1 has some token balance 31 | token.transact({"from": team_multisig}).transfer(customer, 10000) 32 | 33 | return token 34 | 35 | 36 | def test_burn(token_with_customer_balance: Contract, customer: str): 37 | """Burn tokens.""" 38 | 39 | token = token_with_customer_balance 40 | initial_balance = token.call().balanceOf(customer) 41 | initial_supply = token.call().totalSupply() 42 | amount = 1000 43 | 44 | token.transact({"from": customer}).burn(amount) 45 | 46 | assert token.call().balanceOf(customer) == initial_balance - amount 47 | assert token.call().totalSupply() == initial_supply - amount 48 | 49 | events = token.pastEvents("Burned").get() 50 | assert len(events) == 1 51 | e = events[-1] 52 | assert e["args"]["burner"] == customer 53 | assert e["args"]["burnedAmount"] == amount 54 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_bytes_deserializing.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from web3.contract import Contract 3 | 4 | 5 | @pytest.fixture() 6 | def deserializer(chain, presale_crowdsale, uncapped_token, team_multisig) -> Contract: 7 | """Set crowdsale end strategy.""" 8 | 9 | # Create finalizer contract 10 | args = [] 11 | contract, hash = chain.provider.deploy_contract('TestBytesDeserializer', deploy_args=args) 12 | return contract 13 | 14 | 15 | def test_decode_bytes32(deserializer): 16 | """We correctly get bytes 32 back.""" 17 | encoded_payload = 0x01.to_bytes(32, byteorder='little') 18 | bytes32 = deserializer.call().getBytes32(encoded_payload, 0x00) 19 | assert bytes32 =='\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 20 | 21 | 22 | def test_decode_uint256(deserializer): 23 | """We correctly deserializer various uint256 bytes values.""" 24 | 25 | 26 | # Test different values 27 | # 0x01 28 | # 0x0100 29 | # 0x010000 30 | # ... 31 | for i in range(31): 32 | payload = 0x01 << (8*i) 33 | encoded_payload = payload.to_bytes(32, byteorder='big') 34 | value = deserializer.call().getUint256(encoded_payload, 0x00) 35 | assert value == payload, "Did not deserializer correctly: {} {}".format(payload, encoded_payload) 36 | 37 | 38 | def test_offset_uint256(deserializer): 39 | """We correctly deserializer uint256 from different offsets.""" 40 | 41 | for i in range(31): 42 | offset = b"\x00" * i 43 | payload = 0x01 << (8*i) 44 | encoded_payload = offset + payload.to_bytes(32, byteorder='big') 45 | value = deserializer.call().getUint256(encoded_payload, i) 46 | assert value == payload, "Did not deserializer correctly: {} {}".format(payload, encoded_payload) 47 | 48 | 49 | def test_decode_uint128(deserializer): 50 | """We correctly deserializer various uint128 bytes values.""" 51 | 52 | for i in range(15): 53 | payload = 0x01 << (8*i) 54 | encoded_payload = payload.to_bytes(16, byteorder='big') 55 | value = deserializer.call().getUint128(encoded_payload, 0x00) 56 | assert value == payload, "Did not deserializer correctly: {} {}".format(payload, encoded_payload) 57 | 58 | 59 | def test_decode_uint16(deserializer): 60 | """We correctly deserializer various uint32 bytes values.""" 61 | 62 | for i in range(2): 63 | payload = 0x01 << (8*i) 64 | encoded_payload = payload.to_bytes(2, byteorder='big') 65 | value = deserializer.call().getUint16(encoded_payload, 0x00) 66 | assert value == payload, "Did not deserializer correctly: {} {}".format(payload, encoded_payload) 67 | 68 | 69 | def test_decode_uint32(deserializer): 70 | """We correctly deserializer various uint32 bytes values.""" 71 | 72 | for i in range(2): 73 | payload = 0x01 << (8*i) 74 | encoded_payload = payload.to_bytes(2, byteorder='big') 75 | value = deserializer.call().getUint16(encoded_payload, 0x00) 76 | assert value == payload, "Did not deserializer correctly: {} {}".format(payload, encoded_payload) 77 | 78 | 79 | def test_decode_address(deserializer): 80 | """We correctly deserializer Ethereum addresses.""" 81 | address = "0x82A978B3f5962A5b0957d9ee9eEf472EE55B42F1" 82 | 83 | addr_value = int(address, 16) 84 | addr_b = addr_value.to_bytes(20, byteorder="big") # Ethereum address is 20 bytes 85 | value = deserializer.call().getAddress(addr_b, 0) 86 | assert value.lower() == address.lower(), "Did not deserializer correctly: {} {}".format(value, address) 87 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_erc20.py: -------------------------------------------------------------------------------- 1 | """ERC-20 compatibility.""" 2 | 3 | from web3.contract import Contract 4 | 5 | 6 | def test_erc20_interface(token: Contract, token_owner: str, empty_address: str): 7 | """Token satisfies ERC-20 interface.""" 8 | 9 | # https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20.sol 10 | 11 | assert token.call().balanceOf(empty_address) == 0 12 | assert token.call().allowance(token_owner, empty_address) == 0 13 | 14 | # Event 15 | # We follow OpenZeppelin - in the ERO20 issue names are _from, _to, _value 16 | transfer = token._find_matching_event_abi("Transfer", ["from", "to", "value"]) 17 | assert transfer 18 | 19 | approval = token._find_matching_event_abi("Approval", ["owner", "spender", "value"]) 20 | assert approval 21 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_eth_capped.py: -------------------------------------------------------------------------------- 1 | """Token capped crowdsale.""" 2 | import datetime 3 | 4 | import pytest 5 | from eth_utils import from_wei 6 | from eth_utils import to_wei 7 | from ethereum.tester import TransactionFailed 8 | from web3.contract import Contract 9 | 10 | from ico.tests.utils import time_travel 11 | from ico.state import CrowdsaleState 12 | 13 | 14 | @pytest.fixture 15 | def token(empty_token): 16 | """Get a token with 0 initial issuance.""" 17 | return empty_token 18 | 19 | 20 | @pytest.fixture 21 | def start_time() -> int: 22 | """Start Apr 15th""" 23 | return int((datetime.datetime(2017, 4, 15, 16, 00) - datetime.datetime(1970, 1, 1)).total_seconds()) 24 | 25 | 26 | @pytest.fixture 27 | def end_time(start_time) -> int: 28 | """Run 4 weeks.""" 29 | return start_time + 4 * 7 * 24 * 3600 30 | 31 | 32 | @pytest.fixture 33 | def minimum_funding_goal() -> int: 34 | """What is our minimum funding goal.""" 35 | return to_wei(7500, "ether") 36 | 37 | 38 | @pytest.fixture 39 | def cap() -> int: 40 | """What is our maximum tokens sold capacity.""" 41 | return to_wei(888888, "ether") 42 | 43 | 44 | @pytest.fixture 45 | def founder_allocation() -> float: 46 | """How much tokens are allocated to founders, etc.""" 47 | return 7.0/3.0 48 | 49 | 50 | @pytest.fixture 51 | def pricing_strategy(chain, start_time, end_time, team_multisig): 52 | 53 | week = 24*3600 * 7 54 | 55 | args = [ 56 | to_wei(1, "ether") 57 | ] 58 | 59 | tx = { 60 | "from": team_multisig, 61 | } 62 | 63 | contract, hash = chain.provider.deploy_contract('FlatPricing', deploy_args=args, deploy_transaction=tx) 64 | return contract 65 | 66 | 67 | @pytest.fixture 68 | def crowdsale(chain, team_multisig, start_time, end_time, pricing_strategy, preico_cap, minimum_funding_goal, cap, token) -> Contract: 69 | """Create a crowdsale contract that has a minting cap and bonus % and token sold limit.""" 70 | 71 | args = [ 72 | token.address, 73 | pricing_strategy.address, 74 | team_multisig, 75 | start_time, 76 | end_time, 77 | minimum_funding_goal, 78 | cap 79 | ] 80 | 81 | tx = { 82 | "from": team_multisig, 83 | } 84 | 85 | contract, hash = chain.provider.deploy_contract('MintedEthCappedCrowdsale', deploy_args=args, deploy_transaction=tx) 86 | 87 | assert contract.call().owner() == team_multisig 88 | assert not token.call().released() 89 | assert contract.call().weiCap() == cap 90 | 91 | # Allow crowdsale contract to do mint() 92 | token.transact({"from": team_multisig}).setMintAgent(contract.address, True) 93 | assert token.call().mintAgents(contract.address) == True 94 | 95 | return contract 96 | 97 | 98 | @pytest.fixture() 99 | def finalizer(chain, token, crowdsale, team_multisig, founder_allocation) -> Contract: 100 | 101 | # Create finalizer contract 102 | args = [ 103 | token.address, 104 | crowdsale.address, 105 | int(founder_allocation * 10000), # 20% for founders, bounties, etc. 106 | team_multisig, # Who gets extra tokens 107 | ] 108 | contract, hash = chain.provider.deploy_contract('BonusFinalizeAgent', deploy_args=args) 109 | 110 | # Set crowdsale finalizer 111 | 112 | # Allow finalzier to do mint() 113 | token.transact({"from": team_multisig}).setMintAgent(contract.address, True) 114 | assert token.call().mintAgents(contract.address) == True 115 | 116 | token.transact({"from": team_multisig}).setReleaseAgent(contract.address) 117 | 118 | crowdsale.transact({"from": team_multisig}).setFinalizeAgent(contract.address) 119 | 120 | return contract 121 | 122 | 123 | def test_buy_all(chain, web3, crowdsale, token, finalizer, start_time, end_time, team_multisig, customer, cap, founder_allocation): 124 | """Buy all tokens and finalize crowdsale.""" 125 | 126 | # Buy on first week 127 | time_travel(chain, start_time + 1) 128 | assert crowdsale.call().getState() == CrowdsaleState.Funding 129 | 130 | # Buy all cap 131 | wei_value = cap 132 | print(from_wei(web3.eth.getBalance(customer), "ether")) 133 | crowdsale.transact({"from": customer, "value": wei_value}).buy() 134 | assert crowdsale.call().isCrowdsaleFull() 135 | 136 | # Close the deal 137 | time_travel(chain, end_time + 1) 138 | assert crowdsale.call().getState() == CrowdsaleState.Success 139 | crowdsale.transact({"from": team_multisig}).finalize() 140 | assert crowdsale.call().getState() == CrowdsaleState.Finalized 141 | 142 | # See that we counted bonus correctly 143 | team_bonus = token.call().totalSupply() * 7 / 10 144 | assert abs(finalizer.call().allocatedBonus() - team_bonus) < 10 # We lose some in decimal rounding 145 | 146 | # Token is transferable 147 | assert token.call().released() 148 | 149 | 150 | def test_buy_all_plus_one(chain, web3, crowdsale, token, finalizer, start_time, end_time, team_multisig, customer, cap, founder_allocation): 151 | """Buy too many tokens.""" 152 | 153 | # Buy on first week 154 | time_travel(chain, start_time + 1) 155 | assert crowdsale.call().getState() == CrowdsaleState.Funding 156 | 157 | # Buy all cap 158 | wei_value = cap + 1 159 | with pytest.raises(TransactionFailed): 160 | crowdsale.transact({"from": customer, "value": wei_value}).buy() 161 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_finalize.py: -------------------------------------------------------------------------------- 1 | """Finalize crowdsale.""" 2 | import pytest 3 | from ethereum.tester import TransactionFailed 4 | 5 | from populus.chain import TestRPCChain 6 | from web3.contract import Contract 7 | 8 | from ico.tests.utils import time_travel 9 | from ico.state import CrowdsaleState 10 | 11 | 12 | def test_finalize_fail_goal(chain: TestRPCChain, uncapped_flatprice_final: Contract, customer: str, preico_starts_at, preico_ends_at, preico_funding_goal): 13 | """Finalize can be done only for successful crowdsales.""" 14 | 15 | time_travel(chain, preico_starts_at + 1) 16 | wei_value = preico_funding_goal // 2 17 | 18 | uncapped_flatprice_final.transact({"from": customer, "value": wei_value}).buy() 19 | 20 | time_travel(chain, preico_ends_at + 1) 21 | assert uncapped_flatprice_final.call().getState() == CrowdsaleState.Failure 22 | 23 | with pytest.raises(TransactionFailed): 24 | uncapped_flatprice_final.transact().finalize() 25 | 26 | 27 | def test_finalize_success(chain: TestRPCChain, uncapped_flatprice_final: Contract, uncapped_token: Contract, team_multisig: str, customer: str, preico_starts_at, preico_ends_at, preico_funding_goal, default_finalize_agent): 28 | """Finalize releases the token.""" 29 | 30 | time_travel(chain, preico_starts_at + 1) 31 | wei_value = preico_funding_goal 32 | 33 | uncapped_flatprice_final.transact({"from": customer, "value": wei_value}).buy() 34 | 35 | time_travel(chain, preico_ends_at + 1) 36 | assert uncapped_flatprice_final.call().getState() == CrowdsaleState.Success 37 | assert uncapped_flatprice_final.call().finalizeAgent() == default_finalize_agent.address 38 | 39 | # Release the tokens 40 | uncapped_flatprice_final.transact({"from": team_multisig}).finalize() 41 | assert uncapped_flatprice_final.call().getState() == CrowdsaleState.Finalized 42 | 43 | # Here we go 44 | assert uncapped_token.call().released() 45 | assert uncapped_token.call().mintingFinished() 46 | 47 | 48 | def test_finalize_fail_again(chain: TestRPCChain, uncapped_flatprice_final: Contract, team_multisig: str, customer: str, preico_starts_at, preico_ends_at, preico_funding_goal): 49 | """Finalize cannot be done again.""" 50 | 51 | time_travel(chain, preico_starts_at + 1) 52 | wei_value = preico_funding_goal 53 | 54 | uncapped_flatprice_final.transact({"from": customer, "value": wei_value}).buy() 55 | 56 | time_travel(chain, preico_ends_at + 1) 57 | assert uncapped_flatprice_final.call().getState() == CrowdsaleState.Success 58 | 59 | uncapped_flatprice_final.transact({"from": team_multisig}).finalize() 60 | with pytest.raises(TransactionFailed): 61 | uncapped_flatprice_final.transact({"from": team_multisig}).finalize() 62 | 63 | 64 | def test_finalize_only_by_crowdsale(chain: TestRPCChain, uncapped_flatprice_final: Contract, team_multisig: str, customer: str, preico_starts_at, preico_ends_at, preico_funding_goal, default_finalize_agent): 65 | """Finalizer can be only triggered by crowdsale.""" 66 | 67 | time_travel(chain, preico_starts_at + 1) 68 | wei_value = preico_funding_goal 69 | 70 | uncapped_flatprice_final.transact({"from": customer, "value": wei_value}).buy() 71 | 72 | time_travel(chain, preico_ends_at + 1) 73 | assert uncapped_flatprice_final.call().getState() == CrowdsaleState.Success 74 | 75 | # Checks for the owner 76 | with pytest.raises(TransactionFailed): 77 | default_finalize_agent.transact({"from": team_multisig}).finalizeCrowdsale() 78 | 79 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_issuer.py: -------------------------------------------------------------------------------- 1 | """Payment forwarder.""" 2 | import pytest 3 | import datetime 4 | 5 | from ethereum.tester import TransactionFailed 6 | from web3.contract import Contract 7 | 8 | 9 | @pytest.fixture 10 | def token(chain, team_multisig) -> Contract: 11 | """Create the token contract.""" 12 | 13 | args = [team_multisig, "Foobar", "FOOB", 1000000, 0, int((datetime.datetime(2017, 4, 22, 16, 0) - datetime.datetime(1970, 1, 1)).total_seconds())] 14 | 15 | tx = { 16 | "from": team_multisig 17 | } 18 | 19 | contract, hash = chain.provider.deploy_contract('CentrallyIssuedToken', deploy_args=args, deploy_transaction=tx) 20 | 21 | contract.transact({"from": team_multisig}).releaseTokenTransfer() 22 | return contract 23 | 24 | 25 | @pytest.fixture 26 | def issue_script_owner(accounts): 27 | """Ethereum account that interacts with issuer contract.""" 28 | return accounts[8] 29 | 30 | 31 | @pytest.fixture 32 | def issuer(chain, team_multisig, token, issue_script_owner): 33 | args = [issue_script_owner, team_multisig, token.address] 34 | 35 | tx = { 36 | "from": team_multisig 37 | } 38 | 39 | contract, hash = chain.provider.deploy_contract('Issuer', deploy_args=args, deploy_transaction=tx) 40 | 41 | # Set issuance allowance 42 | assert token.call().balanceOf(team_multisig) > 2000 43 | token.transact({"from": team_multisig}).approve(contract.address, 2000) 44 | 45 | return contract 46 | 47 | 48 | def test_issue(web3, issuer, issue_script_owner, customer, token, team_multisig): 49 | """Issue some tokens.""" 50 | 51 | team_multisig_begin = token.call().balanceOf(team_multisig) 52 | assert token.call().allowance(team_multisig, issuer.address) == 2000 53 | assert issuer.call().owner() == issue_script_owner 54 | issuer.transact({"from": issue_script_owner}).issue(customer, 1000) 55 | assert issuer.call().issuedCount() == 1000 56 | assert token.call().balanceOf(customer) == 1000 57 | team_multisig_end = token.call().balanceOf(team_multisig) 58 | assert team_multisig_begin - team_multisig_end == 1000 59 | 60 | 61 | def test_issue_too_many(web3, issuer, issue_script_owner, customer): 62 | """Issue over allowance.""" 63 | with pytest.raises(TransactionFailed): 64 | issuer.transact({"from": issue_script_owner}).issue(customer, 3000) 65 | 66 | 67 | def test_issue_twice(web3, issuer, issue_script_owner, customer): 68 | """Issue some tokens.""" 69 | issuer.transact({"from": issue_script_owner}).issue(customer, 500) 70 | with pytest.raises(TransactionFailed): 71 | issuer.transact({"from": issue_script_owner}).issue(customer, 500) 72 | 73 | 74 | def test_issue_not_an_owner(web3, issuer, customer): 75 | """Somebody tries to issue for themselves.""" 76 | with pytest.raises(TransactionFailed): 77 | issuer.transact({"from": customer}).issue(customer, 500) 78 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_kyc_payload_deserializing.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import pytest 4 | from eth_utils import to_normalized_address, to_checksum_address 5 | from web3.contract import Contract 6 | 7 | from ico.kyc import pack_kyc_dataframe 8 | 9 | 10 | @pytest.fixture() 11 | def kyc_deserializer(chain, presale_crowdsale, uncapped_token, team_multisig) -> Contract: 12 | """Set crowdsale end strategy.""" 13 | 14 | # Create finalizer contract 15 | args = [] 16 | contract, hash = chain.provider.deploy_contract('KYCPayloadDeserializer', deploy_args=args) 17 | return contract 18 | 19 | 20 | @pytest.fixture() 21 | def whitelisted_address(accounts): 22 | return to_checksum_address(accounts[0]) 23 | 24 | 25 | def test_roundtrip_kyc_data(kyc_deserializer, whitelisted_address): 26 | """We correctly encode data in Python side and decode it back in the smart contract.""" 27 | 28 | customer_id = uuid.uuid4() 29 | dataframe = pack_kyc_dataframe(whitelisted_address, customer_id, int(0.1 * 10000), int(9999 * 10000)) 30 | tuple_value = kyc_deserializer.call().getKYCPayload(dataframe) 31 | 32 | # 33 | # Check that the output looks like what we did expect 34 | # 35 | 36 | assert tuple_value[0].lower() == whitelisted_address.lower() 37 | # Do a raw integer comparison, because web3.py and UUID disagree about left padding zeroes 38 | assert tuple_value[1] == customer_id.int 39 | assert tuple_value[2] == 1000 40 | assert tuple_value[3] == 99990000 41 | 42 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_refund.py: -------------------------------------------------------------------------------- 1 | """Refund.""" 2 | 3 | import pytest 4 | from decimal import Decimal 5 | from eth_utils import from_wei 6 | from eth_utils import to_wei 7 | from ethereum.tester import TransactionFailed 8 | from web3 import Web3 9 | 10 | from populus.chain import TestRPCChain 11 | from web3.contract import Contract 12 | 13 | from ico.state import CrowdsaleState 14 | from ico.tests.utils import time_travel 15 | 16 | 17 | #: Max ether we assume will be lost in transaction fees when getting funds back 18 | TRANSACTION_COST_ETH_EPSILON = Decimal(0.01) 19 | 20 | 21 | @pytest.fixture 22 | def failed_ico(chain: TestRPCChain, web3, uncapped_flatprice: Contract, team_multisig, customer, customer_2, preico_starts_at, preico_ends_at, uncapped_flatprice_finalizer) -> Contract: 23 | """An ICO that did not reach a goal, but has participants. 24 | 25 | Both ``customer`` and ``customer_2`` had bought token. 26 | 27 | * customer: 50 ether 28 | * customer_2: 70 ether 29 | * total: 120 ether 30 | * minimum funding goal: 1200 ether 31 | 32 | """ 33 | 34 | time_travel(chain, preico_starts_at + 1) 35 | 36 | uncapped_flatprice.transact({"from": customer, "value": to_wei(50, "ether")}).buy() 37 | uncapped_flatprice.transact({"from": customer_2, "value": to_wei(70, "ether")}).buy() 38 | 39 | assert not uncapped_flatprice.call().isMinimumGoalReached() 40 | 41 | # Make sure customer 1 has some token balance 42 | time_travel(chain, uncapped_flatprice.call().endsAt() + 1) 43 | return uncapped_flatprice 44 | 45 | 46 | @pytest.fixture 47 | def failed_ico_ready_to_refund(chain: TestRPCChain, failed_ico: Contract, team_multisig) -> Contract: 48 | """An ICO that did not reach a goal, but has participants. 49 | 50 | The team has moved funds back from the multisig wallet on the crowdsale contract. Note that due to transaction fees you need to pay a minimal transaction cost out of your own pocket. 51 | """ 52 | failed_ico.transact({"from" : team_multisig, "value": failed_ico.call().weiRaised()}).loadRefund() 53 | return failed_ico 54 | 55 | 56 | def test_can_begin_refund(failed_ico, web3: Web3, team_multisig): 57 | """After a failure the contract is ready to load refunds.""" 58 | 59 | assert failed_ico.call().getState() == CrowdsaleState.Failure 60 | assert failed_ico.call().weiRaised() == to_wei(120, "ether") 61 | assert web3.eth.getBalance(failed_ico.address) == 0 62 | 63 | 64 | def test_too_small_refund(failed_ico, web3: Web3, team_multisig, customer): 65 | """All refund must be loaded before the refund can begin.""" 66 | 67 | assert failed_ico.call().getState() == CrowdsaleState.Failure 68 | failed_ico.transact({"from": team_multisig, "value": failed_ico.call().weiRaised() // 2}).loadRefund() 69 | assert failed_ico.call().getState() == CrowdsaleState.Failure 70 | 71 | # Customer tries to reclaim refunds early 72 | with pytest.raises(TransactionFailed): 73 | failed_ico.transact({"from": customer}).refund() 74 | 75 | 76 | def test_refund(failed_ico_ready_to_refund: Contract, web3: Web3, customer: str, customer_2: str): 77 | """Customers can claim their refunds.""" 78 | 79 | assert failed_ico_ready_to_refund.call().loadedRefund() == to_wei(120, "ether") 80 | assert failed_ico_ready_to_refund.call().getState() == CrowdsaleState.Refunding 81 | 82 | # Check that the customer gets money back 83 | invested_amount = failed_ico_ready_to_refund.call().investedAmountOf(customer) 84 | begin_balance = web3.eth.getBalance(customer) 85 | failed_ico_ready_to_refund.transact({"from": customer}).refund() 86 | end_balance = web3.eth.getBalance(customer) 87 | 88 | eth = from_wei(end_balance - begin_balance, "ether") # Decimal('49.999999999999954693') 89 | assert (end_balance - begin_balance) >= eth - TRANSACTION_COST_ETH_EPSILON 90 | 91 | failed_ico_ready_to_refund.transact({"from": customer_2}).refund() 92 | 93 | # Everything has been refunded 94 | assert failed_ico_ready_to_refund.call().weiRefunded() == to_wei(120, "ether") 95 | 96 | 97 | def test_cannot_refund_twice(failed_ico_ready_to_refund: Contract, customer: str): 98 | """Customer can reclaim refund only once.""" 99 | 100 | assert failed_ico_ready_to_refund.call().getState() == CrowdsaleState.Refunding 101 | 102 | failed_ico_ready_to_refund.transact({"from": customer}).refund() 103 | with pytest.raises(TransactionFailed): 104 | failed_ico_ready_to_refund.transact({"from": customer}).refund() 105 | 106 | 107 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_releasable.py: -------------------------------------------------------------------------------- 1 | """Releasable token.""" 2 | 3 | import pytest 4 | from ethereum.tester import TransactionFailed 5 | from web3.contract import Contract 6 | 7 | 8 | def test_bad_released(token: Contract, team_multisig: str, malicious_address: str, empty_address: str): 9 | """Only release agent can make token transferable.""" 10 | 11 | assert not token.call().released() 12 | 13 | with pytest.raises(TransactionFailed): 14 | token.transact({"from": malicious_address}).releaseTokenTransfer() 15 | 16 | # Even owner cannot release, need to go through release agent process 17 | with pytest.raises(TransactionFailed): 18 | token.transact({"from": team_multisig}).releaseTokenTransfer() 19 | 20 | 21 | def test_released(released_token: Contract, customer: str, empty_address: str): 22 | """Released token is free to transfer.""" 23 | token = released_token 24 | assert token.call().released() 25 | 26 | 27 | def test_transfer(released_token: Contract, customer: str, empty_address: str): 28 | """ERC-20 compatible transfer() is available.""" 29 | 30 | token = released_token 31 | amount = 5000 32 | initial_balance = token.call().balanceOf(customer) 33 | 34 | token.transact({"from": customer}).transfer(empty_address, amount) 35 | 36 | assert token.call().balanceOf(customer) == initial_balance - amount 37 | assert token.call().balanceOf(empty_address) == amount 38 | 39 | events = token.pastEvents("Transfer").get() 40 | assert len(events) == 1 + 1 # plus initial release 41 | e = events[-1] 42 | assert e["args"]["to"] == empty_address 43 | assert e["args"]["from"] == customer 44 | assert e["args"]["value"] == amount 45 | 46 | 47 | def test_cannot_transfer(token: Contract, team_multisig, customer: str, customer_2: str): 48 | """Tokens cannot be transferred before they are released.""" 49 | 50 | assert not token.call().released() 51 | 52 | # team_multisig is on the whitelisted transfer agent list 53 | assert token.call().transferAgents(team_multisig) == False 54 | with pytest.raises(TransactionFailed): 55 | token.transact({"from": team_multisig}).transfer(customer, 10000) 56 | 57 | # customer cannot transfer to customer 2 before release 58 | assert token.call().transferAgents(customer) == False 59 | with pytest.raises(TransactionFailed): 60 | token.transact({"from": customer}).transfer(customer_2, 10000) 61 | 62 | 63 | def test_not_enough_balance(released_token: Contract, customer: str, empty_address: str): 64 | """ERC-20 transfer fails if user exceeds his/her balance.""" 65 | 66 | token = released_token 67 | initial_balance = token.call().balanceOf(customer) 68 | amount = initial_balance + 1 69 | 70 | with pytest.raises(TransactionFailed): 71 | token.transact({"from": customer}).transfer(empty_address, amount) 72 | 73 | 74 | def test_transfer_with_allowance(released_token: Contract, customer: str, empty_address: str, allowed_party): 75 | """Tokens can be transferred with ECR-20 allowance approval.""" 76 | 77 | token = released_token 78 | amount = 5000 79 | initial_balance = token.call().balanceOf(customer) 80 | token.transact({"from": customer}).approve(allowed_party, amount) 81 | assert token.call().allowance(customer, allowed_party) == amount 82 | 83 | events = token.pastEvents("Approval").get() 84 | assert len(events) > 0 # Edgeless gets 2 events, because one is needed to construct token 85 | e = events[-1] 86 | assert e["args"]["owner"] == customer 87 | assert e["args"]["spender"] == allowed_party 88 | assert e["args"]["value"] == amount 89 | 90 | token.transact({"from": allowed_party}).transferFrom(customer, empty_address, amount) 91 | 92 | events = token.pastEvents("Transfer").get() 93 | assert len(events) == 1 + 1 # plus initial transfer 94 | e = events[-1] 95 | assert e["args"]["to"] == empty_address 96 | assert e["args"]["from"] == customer 97 | assert e["args"]["value"] == amount 98 | 99 | assert token.call().balanceOf(customer) == initial_balance - amount 100 | assert token.call().balanceOf(empty_address) == amount 101 | assert token.call().allowance(customer, allowed_party) == 0 102 | 103 | 104 | def test_transfer_with_allowance_exceeded(released_token: Contract, customer: str, empty_address: str, allowed_party): 105 | """One cannot transfers more than approved allowance.""" 106 | 107 | token = released_token 108 | amount = 5000 109 | token.transact({"from": customer}).approve(allowed_party, amount) 110 | 111 | with pytest.raises(TransactionFailed): 112 | token.transact({"from": allowed_party}).transferFrom(customer, empty_address, amount+1) 113 | 114 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_require_customer_id.py: -------------------------------------------------------------------------------- 1 | """Customer id tracking.""" 2 | import uuid 3 | 4 | import pytest 5 | from ethereum.tester import TransactionFailed 6 | from eth_utils import to_wei 7 | 8 | from ico.tests.utils import time_travel 9 | from ico.state import CrowdsaleState 10 | 11 | from sha3 import keccak_256 12 | from rlp.utils import decode_hex 13 | 14 | @pytest.fixture 15 | def crowdsale(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig): 16 | """Set up a crowdsale with customer id require policy.""" 17 | uncapped_flatprice.transact({"from": team_multisig}).setRequireCustomerId(True) 18 | return uncapped_flatprice 19 | 20 | 21 | @pytest.fixture 22 | def token(uncapped_token): 23 | """Token contract we are buying.""" 24 | return uncapped_token 25 | 26 | 27 | @pytest.fixture 28 | def customer_id(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig) -> int: 29 | """Generate UUID v4 customer id as a hex string.""" 30 | customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4 31 | return customer_id 32 | 33 | 34 | def test_only_owner_change_change_policy(crowdsale, customer): 35 | """Only owner change change customerId required policy.""" 36 | 37 | with pytest.raises(TransactionFailed): 38 | crowdsale.transact({"from": customer}).setRequireCustomerId(False) 39 | 40 | 41 | def test_participate_with_customer_id(chain, crowdsale, customer, customer_id, token): 42 | """Buy tokens with a proper customer id.""" 43 | 44 | time_travel(chain, crowdsale.call().startsAt() + 1) 45 | wei_value = to_wei(1, "ether") 46 | assert crowdsale.call().getState() == CrowdsaleState.Funding 47 | checksumbyte = keccak_256(decode_hex(format(customer_id, 'x').zfill(32))).digest()[:1] 48 | crowdsale.transact({"from": customer, "value": wei_value}).buyWithCustomerIdWithChecksum(customer_id, checksumbyte) 49 | 50 | # We got credited 51 | assert token.call().balanceOf(customer) > 0 52 | 53 | # We have tracked the investor id 54 | events = crowdsale.pastEvents("Invested").get() 55 | assert len(events) == 1 56 | e = events[0] 57 | assert e["args"]["investor"] == customer 58 | assert e["args"]["weiAmount"] == wei_value 59 | assert e["args"]["customerId"] == customer_id 60 | 61 | 62 | def test_participate_missing_customer_id(chain, crowdsale, customer, customer_id, token): 63 | """Cannot bypass customer id process.""" 64 | 65 | time_travel(chain, crowdsale.call().startsAt() + 1) 66 | wei_value = to_wei(1, "ether") 67 | assert crowdsale.call().getState() == CrowdsaleState.Funding 68 | 69 | with pytest.raises(TransactionFailed): 70 | crowdsale.transact({"from": customer, "value": wei_value}).buy() 71 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_require_signed_address.py: -------------------------------------------------------------------------------- 1 | """Signed address investing.""" 2 | 3 | import uuid 4 | 5 | import binascii 6 | import bitcoin 7 | import pytest 8 | from eth_utils import force_bytes 9 | from ethereum.tester import TransactionFailed 10 | from eth_utils import to_wei 11 | 12 | from ico.tests.utils import time_travel 13 | from ico.state import CrowdsaleState 14 | from ico.sign import get_ethereum_address_from_private_key 15 | from ico.sign import get_address_as_bytes 16 | from ico.sign import sign 17 | 18 | 19 | @pytest.fixture 20 | def private_key(): 21 | """Server side private key.""" 22 | return "Toholampi summer festival 2017 has the most harcore rock bands" 23 | 24 | 25 | @pytest.fixture 26 | def signer_address(private_key): 27 | """Server side signer address.""" 28 | return get_ethereum_address_from_private_key(private_key) 29 | 30 | 31 | @pytest.fixture 32 | def crowdsale(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig, signer_address): 33 | """Set up a crowdsale with customer id require policy.""" 34 | uncapped_flatprice.transact({"from": team_multisig}).setRequireSignedAddress(True, signer_address) 35 | return uncapped_flatprice 36 | 37 | 38 | @pytest.fixture 39 | def token(uncapped_token): 40 | """Token contract we are buying.""" 41 | return uncapped_token 42 | 43 | 44 | @pytest.fixture 45 | def customer_id(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig) -> int: 46 | """Generate UUID v4 customer id as a hex string.""" 47 | customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4 48 | return customer_id 49 | 50 | 51 | @pytest.fixture 52 | def pad_contract(chain): 53 | """Token contract we are buying.""" 54 | contract, hash = chain.provider.deploy_contract('TestSolidityAddressHash') 55 | return contract 56 | 57 | 58 | def test_only_owner_change_change_policy(crowdsale, customer, signer_address): 59 | """Only owner change change customerId required policy.""" 60 | 61 | with pytest.raises(TransactionFailed): 62 | crowdsale.transact({"from": customer}).setRequireSignedAddress(True, signer_address) 63 | 64 | 65 | def test_participate_with_signed_address(chain, crowdsale, customer, customer_id, token, private_key): 66 | """Buy tokens with a proper signed address.""" 67 | 68 | address_bytes = get_address_as_bytes(customer) 69 | sign_data = sign(address_bytes, private_key) 70 | 71 | time_travel(chain, crowdsale.call().startsAt() + 1) 72 | wei_value = to_wei(1, "ether") 73 | assert crowdsale.call().getState() == CrowdsaleState.Funding 74 | crowdsale.transact({"from": customer, "value": wei_value}).buyWithSignedAddress(customer_id, sign_data["v"], sign_data["r_bytes"], sign_data["s_bytes"]) 75 | 76 | # We got credited 77 | assert token.call().balanceOf(customer) > 0 78 | 79 | # We have tracked the investor id 80 | events = crowdsale.pastEvents("Invested").get() 81 | assert len(events) == 1 82 | e = events[0] 83 | assert e["args"]["investor"] == customer 84 | assert e["args"]["weiAmount"] == wei_value 85 | assert e["args"]["customerId"] == customer_id 86 | 87 | 88 | def test_participate_bad_signature(chain, crowdsale, customer, customer_id, token): 89 | """Investment does not happen with a bad signature..""" 90 | 91 | address_bytes = get_address_as_bytes(customer) 92 | sign_data = sign(address_bytes, private_key) 93 | 94 | time_travel(chain, crowdsale.call().startsAt() + 1) 95 | wei_value = to_wei(1, "ether") 96 | assert crowdsale.call().getState() == CrowdsaleState.Funding 97 | 98 | sign_data["s_bytes"] = b'ABC' # Corrupt signature data 99 | 100 | with pytest.raises(TransactionFailed): 101 | crowdsale.transact({"from": customer, "value": wei_value}).buyWithSignedAddress(customer_id, sign_data["v"], sign_data["r_bytes"], sign_data["s_bytes"]) 102 | 103 | 104 | def test_left_pad(pad_contract): 105 | """Ensure we handle leading zero in the address correctly.""" 106 | 107 | address_bytes = get_address_as_bytes(pad_contract.call().leftPad()) 108 | hash = bitcoin.bin_sha256(address_bytes) 109 | val = pad_contract.call().getHashLeftPad() 110 | val = force_bytes(val) 111 | assert hash == val 112 | 113 | 114 | @pytest.mark.skip(reason="Solidity compiler 0.4.11 does not run this yet") 115 | def test_right_pad(pad_contract): 116 | """Ensure we handle trailing zero in the address correctly.""" 117 | 118 | address_bytes = get_address_as_bytes(pad_contract.call().rightPad()) 119 | hash = bitcoin.bin_sha256(address_bytes) 120 | val = pad_contract.call().getHashRightPad() 121 | val = force_bytes(val) 122 | assert hash == val 123 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_time_vault.py: -------------------------------------------------------------------------------- 1 | """Time vault functionality.""" 2 | import datetime 3 | 4 | import pytest 5 | from ethereum.tester import TransactionFailed 6 | from web3.contract import Contract 7 | 8 | from ico.tests.utils import time_travel 9 | 10 | 11 | @pytest.fixture 12 | def token(chain, team_multisig): 13 | args = [ 14 | team_multisig, 15 | "Token", 16 | "TKN", 17 | 1000000, 18 | 0, 19 | int((datetime.datetime(2017, 4, 22, 16, 0) - datetime.datetime(1970, 1, 1)).total_seconds()) 20 | ] 21 | contract, hash = chain.provider.deploy_contract('CentrallyIssuedToken', deploy_args=args) 22 | assert contract.call().balanceOf(team_multisig) == 1000000 23 | 24 | contract.transact({"from": team_multisig}).releaseTokenTransfer() 25 | return contract 26 | 27 | 28 | @pytest.fixture 29 | def unlock_time(): 30 | """UNIX timestamp to unlock tokens 180 days in the future.""" 31 | return int((datetime.datetime.now() + datetime.timedelta(days=180) - datetime.datetime(1970, 1, 1)).total_seconds()) 32 | 33 | 34 | @pytest.fixture 35 | def vault(chain, team_multisig, token, unlock_time): 36 | """Deploy a loaded vault contract and move all tokens there.""" 37 | args = [ 38 | team_multisig, 39 | token.address, 40 | unlock_time 41 | ] 42 | 43 | contract, hash = chain.provider.deploy_contract('TimeVault', deploy_args=args) 44 | 45 | # Load all tokens to the vault 46 | token.transact({"from": team_multisig}).transfer(contract.address, 1000000) 47 | 48 | return contract 49 | 50 | 51 | def test_unlock_early(chain, token: Contract, team_multisig: str, vault: Contract, unlock_time: int): 52 | """Early unlock fails.""" 53 | 54 | assert token.call().balanceOf(team_multisig) == 0 55 | assert token.call().balanceOf(vault.address) == 1000000 56 | 57 | time_travel(chain, unlock_time - 1) 58 | with pytest.raises(TransactionFailed): 59 | vault.transact({"from": team_multisig}).unlock() 60 | 61 | 62 | def test_unlock(chain, token: Contract, team_multisig: str, vault: Contract, unlock_time: int): 63 | """Unlock tokens.""" 64 | 65 | assert token.call().balanceOf(team_multisig) == 0 66 | assert token.call().balanceOf(vault.address) == 1000000 67 | 68 | time_travel(chain, unlock_time + 1) 69 | vault.transact({"from": team_multisig}).unlock() 70 | 71 | assert token.call().balanceOf(team_multisig) == 1000000 72 | assert token.call().balanceOf(vault.address) == 0 73 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_token.py: -------------------------------------------------------------------------------- 1 | """Token core functionality.""" 2 | 3 | import pytest 4 | from ethereum.tester import TransactionFailed 5 | from web3.contract import Contract 6 | 7 | @pytest.fixture 8 | def token_new_name() -> str: 9 | return "New name" 10 | 11 | 12 | @pytest.fixture 13 | def token_new_symbol() -> str: 14 | return "NEW" 15 | 16 | 17 | def test_token_initialized(token: Contract, team_multisig: str, token_symbol: str, token_name: str, initial_supply: int): 18 | """Token is initialized with the parameters we want.""" 19 | assert token.call().owner() == team_multisig 20 | assert token.call().symbol() == token_symbol 21 | assert token.call().name() == token_name 22 | assert token.call().totalSupply() == initial_supply 23 | 24 | 25 | def test_malicious_transfer_agent_set(token: Contract, malicious_address): 26 | """Externals cannot whitelist transfer agents.""" 27 | 28 | with pytest.raises(TransactionFailed): 29 | token.transact({"from": malicious_address}).setTransferAgent(malicious_address, True) 30 | 31 | def test_token_rename(token: Contract, team_multisig, token_new_name, token_new_symbol): 32 | """We will update token's information here""" 33 | 34 | token.transact({"from": team_multisig}).setTokenInformation(token_new_name, token_new_symbol) 35 | 36 | assert token.call().name() == token_new_name 37 | assert token.call().symbol() == token_new_symbol 38 | -------------------------------------------------------------------------------- /ico/tests/contracts/test_upgrade.py: -------------------------------------------------------------------------------- 1 | """Upgradeable token.""" 2 | 3 | import pytest 4 | from ethereum.tester import TransactionFailed 5 | from web3.contract import Contract 6 | 7 | from ico.state import UpgradeState 8 | 9 | 10 | @pytest.fixture 11 | def upgrade_agent(chain, released_token) -> Contract: 12 | """The test upgrade agent/target token.""" 13 | args = [ 14 | released_token.address, 15 | ] 16 | contract, hash = chain.provider.deploy_contract('TestMigrationTarget', deploy_args=args) 17 | return contract 18 | 19 | 20 | @pytest.fixture 21 | def upgrade_agent_2(chain, released_token) -> Contract: 22 | """Another deployment of the upgrade agent.""" 23 | args = [ 24 | released_token.address, 25 | ] 26 | contract, hash = chain.provider.deploy_contract('TestMigrationTarget', deploy_args=args) 27 | return contract 28 | 29 | 30 | def test_cannot_upgrade_until_released(token: Contract): 31 | """Non-released tokens cannot be upgradeable.""" 32 | 33 | assert not token.call().canUpgrade() 34 | assert token.call().getUpgradeState() == UpgradeState.NotAllowed 35 | 36 | 37 | def test_can_upgrade_released_token(released_token: Contract): 38 | """Released token is free to upgrade.""" 39 | assert released_token.call().canUpgrade() 40 | assert released_token.call().getUpgradeState() == UpgradeState.WaitingForAgent 41 | 42 | 43 | def test_set_upgrade_agent(released_token: Contract, upgrade_agent: Contract, team_multisig): 44 | """Upgrade agent can be set on a released token.""" 45 | 46 | # Preconditions are met 47 | assert upgrade_agent.call().isUpgradeAgent() 48 | assert released_token.call().canUpgrade() 49 | assert released_token.call().upgradeMaster() == team_multisig 50 | assert upgrade_agent.call().oldToken() == released_token.address 51 | assert upgrade_agent.call().originalSupply() == released_token.call().totalSupply() 52 | 53 | released_token.transact({"from": team_multisig}).setUpgradeAgent(upgrade_agent.address) 54 | assert released_token.call().getUpgradeState() == UpgradeState.ReadyToUpgrade 55 | 56 | 57 | def test_malicious_set_upgrade_agent(released_token: Contract, upgrade_agent: Contract, malicious_address): 58 | """Only owner can set the upgrade agent can be set on a released token.""" 59 | 60 | with pytest.raises(TransactionFailed): 61 | released_token.transact({"from": malicious_address}).setUpgradeAgent(upgrade_agent.address) 62 | 63 | 64 | def test_change_upgrade_master(released_token: Contract, upgrade_agent: Contract, team_multisig, customer): 65 | """Owner can change the upgrade master.""" 66 | 67 | released_token.transact({"from": team_multisig}).setUpgradeMaster(customer) 68 | released_token.transact({"from": customer}).setUpgradeAgent(upgrade_agent.address) 69 | 70 | 71 | def test_upgrade_partial(released_token: Contract, upgrade_agent: Contract, team_multisig, customer): 72 | """We can upgrade some of tokens.""" 73 | 74 | released_token.transact({"from": team_multisig}).setUpgradeAgent(upgrade_agent.address) 75 | assert released_token.call().balanceOf(team_multisig) == 9990000 76 | assert released_token.call().totalSupply() == 10000000 77 | released_token.transact({"from": team_multisig}).upgrade(3000000) 78 | 79 | assert released_token.call().getUpgradeState() == UpgradeState.Upgrading 80 | 81 | assert released_token.call().totalSupply() == 7000000 82 | assert upgrade_agent.call().totalSupply() == 3000000 83 | assert released_token.call().totalUpgraded() == 3000000 84 | 85 | assert released_token.call().balanceOf(team_multisig) == 6990000 86 | assert upgrade_agent.call().balanceOf(team_multisig) == 3000000 87 | 88 | 89 | def test_upgrade_all(released_token: Contract, upgrade_agent: Contract, team_multisig, customer): 90 | """We can upgrade all tokens of two owners.""" 91 | 92 | released_token.transact({"from": team_multisig}).setUpgradeAgent(upgrade_agent.address) 93 | assert released_token.call().balanceOf(team_multisig) == 9990000 94 | assert released_token.call().balanceOf(customer) == 10000 95 | assert released_token.call().totalSupply() == 10000000 96 | released_token.transact({"from": team_multisig}).upgrade(9990000) 97 | released_token.transact({"from": customer}).upgrade(10000) 98 | 99 | assert released_token.call().getUpgradeState() == UpgradeState.Upgrading 100 | assert released_token.call().totalSupply() == 0 101 | assert upgrade_agent.call().totalSupply() == 10000000 102 | assert released_token.call().totalUpgraded() == 10000000 103 | 104 | assert upgrade_agent.call().balanceOf(team_multisig) == 9990000 105 | assert upgrade_agent.call().balanceOf(customer) == 10000 106 | 107 | 108 | def test_cannot_upgrade_too_many(released_token: Contract, upgrade_agent: Contract, team_multisig, customer): 109 | """We cannot upgrade more tokens than we have.""" 110 | 111 | released_token.transact({"from": team_multisig}).setUpgradeAgent(upgrade_agent.address) 112 | assert released_token.call().balanceOf(customer) == 10000 113 | 114 | with pytest.raises(TransactionFailed): 115 | released_token.transact({"from": customer}).upgrade(20000) 116 | 117 | 118 | def test_cannot_change_agent_in_fly(released_token: Contract, upgrade_agent: Contract, team_multisig, customer, upgrade_agent_2): 119 | """Upgrade agent cannot be changed after the ugprade has begun.""" 120 | 121 | released_token.transact({"from": team_multisig}).setUpgradeAgent(upgrade_agent.address) 122 | released_token.transact({"from": customer}).upgrade(10000) 123 | 124 | with pytest.raises(TransactionFailed): 125 | released_token.transact({"from": team_multisig}).setUpgradeAgent(upgrade_agent_2.address) 126 | 127 | -------------------------------------------------------------------------------- /ico/tests/fixtures/amltoken.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pytest 4 | from eth_utils import to_wei 5 | from web3.contract import Contract 6 | 7 | @pytest.fixture 8 | def aml_token(chain, team_multisig, token_name, token_symbol, initial_supply) -> Contract: 9 | """Create the token contract.""" 10 | 11 | args = [token_name, token_symbol, initial_supply, 0, True] # Owner set 12 | 13 | tx = { 14 | "from": team_multisig 15 | } 16 | 17 | contract, hash = chain.provider.deploy_contract('AMLToken', deploy_args=args, deploy_transaction=tx) 18 | return contract 19 | 20 | @pytest.fixture 21 | def released_aml_token(chain, team_multisig, aml_token, release_agent, customer) -> Contract: 22 | """Create a Crowdsale token where transfer restrictions have been lifted.""" 23 | 24 | aml_token.transact({"from": team_multisig}).setReleaseAgent(team_multisig) 25 | aml_token.transact({"from": team_multisig}).releaseTokenTransfer() 26 | 27 | # Make sure customer 1 has some token balance 28 | aml_token.transact({"from": team_multisig}).transfer(customer, 10000) 29 | 30 | return aml_token 31 | -------------------------------------------------------------------------------- /ico/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 | -------------------------------------------------------------------------------- /ico/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 | -------------------------------------------------------------------------------- /ico/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 | 32 | @pytest.fixture 33 | def beneficiary(accounts) -> str: 34 | """The team control address.""" 35 | return accounts[3] 36 | 37 | 38 | @pytest.fixture 39 | def team_multisig(accounts) -> str: 40 | """The team multisig address.""" 41 | return accounts[4] 42 | 43 | 44 | @pytest.fixture 45 | def malicious_address(accounts) -> str: 46 | """Somebody who tries to perform activities they are not allowed to.""" 47 | return accounts[5] 48 | 49 | 50 | @pytest.fixture 51 | def empty_address(accounts): 52 | """This account never holds anything.""" 53 | return accounts[6] 54 | 55 | 56 | @pytest.fixture 57 | def allowed_party(accounts): 58 | """Gets ERC-20 allowance.""" 59 | return accounts[7] 60 | 61 | 62 | 63 | # 64 | # ERC-20 fixtures 65 | # 66 | 67 | @pytest.fixture 68 | def token_owner(beneficiary): 69 | return beneficiary 70 | 71 | 72 | @pytest.fixture 73 | def token(chain, team_multisig, token_name, token_symbol, initial_supply) -> Contract: 74 | """Create the token contract.""" 75 | 76 | args = [token_name, token_symbol, initial_supply, 0, True] # Owner set 77 | 78 | tx = { 79 | "from": team_multisig 80 | } 81 | 82 | contract, hash = chain.provider.deploy_contract('CrowdsaleToken', deploy_args=args, deploy_transaction=tx) 83 | return contract 84 | 85 | 86 | @pytest.fixture 87 | def empty_token(chain, team_multisig, token_name, token_symbol) -> Contract: 88 | """Create the token contract without initial supply.""" 89 | 90 | args = [token_name, token_symbol, 0, 0, True] # Owner set 91 | 92 | tx = { 93 | "from": team_multisig 94 | } 95 | 96 | contract, hash = chain.provider.deploy_contract('CrowdsaleToken', deploy_args=args, deploy_transaction=tx) 97 | return contract 98 | -------------------------------------------------------------------------------- /ico/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_milestone_pricing(chain, presale_fund_collector, uncapped_flatprice, presale_freeze_ends_at, team_multisig): 16 | """Pricing used in presale tests, allowing us to set special price for presale participants.""" 17 | 18 | week = 24 * 3600 * 7 19 | start_time = uncapped_flatprice.call().startsAt() 20 | end_time = start_time + week*4 21 | 22 | uncapped_flatprice.transact({"from": team_multisig}).setEndsAt(end_time) 23 | 24 | args = [ 25 | [ 26 | start_time + 0, to_wei("0.10", "ether"), 27 | start_time + week*1, to_wei("0.10", "ether"), 28 | start_time + week*2, to_wei("0.10", "ether"), 29 | start_time + week*3, to_wei("0.10", "ether"), 30 | end_time, to_wei("0", "ether"), 31 | ], 32 | ] 33 | 34 | tx = { 35 | "gas": 4000000, 36 | "from": team_multisig 37 | } 38 | contract, hash = chain.provider.deploy_contract('MilestonePricing', deploy_args=args, deploy_transaction=tx) 39 | contract.transact({"from": team_multisig}).setPreicoAddress(presale_fund_collector.address, to_wei("0.05", "ether")) 40 | 41 | assert contract.call().isSane(uncapped_flatprice.address) 42 | return contract 43 | 44 | 45 | @pytest.fixture 46 | def presale_fund_collector(chain, presale_freeze_ends_at, team_multisig) -> Contract: 47 | """In actual ICO, the price is doubled (for testing purposes).""" 48 | args = [ 49 | team_multisig, 50 | presale_freeze_ends_at, 51 | to_wei(1, "ether") 52 | ] 53 | tx = { 54 | "from": team_multisig, 55 | } 56 | presale_fund_collector, hash = chain.provider.deploy_contract('PresaleFundCollector', deploy_args=args, deploy_transaction=tx) 57 | return presale_fund_collector 58 | 59 | 60 | @pytest.fixture 61 | def presale_crowdsale(chain, presale_fund_collector, uncapped_flatprice, team_multisig): 62 | """ICO associated with the presale where funds will be moved to a presale.""" 63 | presale_fund_collector.transact({"from": team_multisig}).setCrowdsale(uncapped_flatprice.address) 64 | return uncapped_flatprice 65 | 66 | 67 | @pytest.fixture 68 | def presale_crowdsale_miletstoned(chain, presale_fund_collector, uncapped_flatprice, presale_milestone_pricing, team_multisig): 69 | """ICO associated with the presale where funds will be moved to a presale. 70 | 71 | We set a special milestone pricing that allows us to control the pricing for the presale participants. 72 | """ 73 | uncapped_flatprice.transact({"from": team_multisig}).setPricingStrategy(presale_milestone_pricing.address) 74 | presale_fund_collector.transact({"from": team_multisig}).setCrowdsale(uncapped_flatprice.address) 75 | presale_milestone_pricing.transact({"from" : team_multisig}).setPreicoAddress(presale_fund_collector.address, to_wei("0.08", "ether")) 76 | return uncapped_flatprice 77 | 78 | -------------------------------------------------------------------------------- /ico/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 | -------------------------------------------------------------------------------- /ico/tests/tools/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def example_yaml_filename() -> str: 8 | """Example yml definition file.""" 9 | return os.path.join(os.getcwd(), "crowdsales", "unit-test.yml") 10 | -------------------------------------------------------------------------------- /ico/tests/tools/manual_etherscan.py: -------------------------------------------------------------------------------- 1 | from ico.etherscan import verify_contract 2 | from populus import Project 3 | 4 | 5 | def manual_etherscan(): 6 | """Manual test verification on EtherScan.io.""" 7 | 8 | contract_name = "PresaleFundCollector" 9 | address = "0xb589ef3af084cc5ec905d23112520ec168478582" 10 | constructor_args = "000000000000000000000000e8baf9df0ded92c5f28aab97f13936e7716a4a5b00000000000000000000000000000000000000000000000000000000590ba32f000000000000000000000000000000000000000000000002b5e3af16b1880000" 11 | libraries = {'SafeMathLib': '0x8fd011ad5d39da2f0a09c2d89e7d6ae861fe42ba'} 12 | 13 | p = Project() 14 | chain_name = "mainnet" 15 | 16 | verify_contract( 17 | project=p, 18 | chain_name=chain_name, 19 | address=address, 20 | contract_name="PresaleFundCollector", 21 | contract_filename="PresaleFundCollector.sol", 22 | constructor_args=constructor_args, 23 | libraries=libraries) 24 | 25 | 26 | if __name__ == "__main__": 27 | manual_etherscan() 28 | -------------------------------------------------------------------------------- /ico/tests/tools/test_crowdsale_deployer.py: -------------------------------------------------------------------------------- 1 | """YAML crowdsale definition loader.""" 2 | 3 | from ico.deploy import deploy_crowdsale 4 | from ico.deploy import write_deployment_report 5 | from ico.deploy import perform_post_actions 6 | from ico.deploy import perform_verify_actions 7 | from ico.definition import load_crowdsale_definitions 8 | 9 | 10 | def test_deploy_crowdsale(project, chain, accounts, example_yaml_filename): 11 | """Deploy multiple contracts from a crowdsale definition file.""" 12 | 13 | chain_data = load_crowdsale_definitions(example_yaml_filename, "unit_test") 14 | # Not needed for testrpc 15 | chain_data["unlock_deploy_address"] = False 16 | 17 | runtime_data, statistics, contracts = deploy_crowdsale(project, chain, example_yaml_filename, chain_data, accounts[7]) 18 | 19 | perform_post_actions(chain, runtime_data, contracts) 20 | perform_verify_actions(chain, runtime_data, contracts) 21 | write_deployment_report(example_yaml_filename, runtime_data) 22 | -------------------------------------------------------------------------------- /ico/tests/tools/test_expand.py: -------------------------------------------------------------------------------- 1 | """Test import expansion tool.""" 2 | 3 | from populus import Project 4 | 5 | from ico.importexpand import expand_contract_imports 6 | 7 | 8 | def test_expand_crowdsale_contract(project: Project): 9 | """Expand import statement in a Solidity source file.""" 10 | 11 | expanded, imported_files = expand_contract_imports(project, "Crowdsale.sol") 12 | 13 | assert "contract Crowdsale" in expanded 14 | assert 'import "' not in expanded 15 | 16 | 17 | def test_expand_token(project: Project): 18 | """Expand import statement in a Solidity source file, using Token contract.""" 19 | 20 | expanded, imported_files = expand_contract_imports(project, "CrowdsaleToken.sol") 21 | assert "contract CrowdsaleToken" in expanded 22 | assert 'import "' not in expanded 23 | assert "import '" not in expanded 24 | 25 | 26 | -------------------------------------------------------------------------------- /ico/tests/tools/test_libraries.py: -------------------------------------------------------------------------------- 1 | """Library extraction from deployed contracts.""" 2 | from populus.chain import TestRPCChain 3 | from web3.contract import Contract 4 | 5 | from ico.utils import get_libraries 6 | 7 | 8 | def test_extract_libraries(chain: TestRPCChain, uncapped_flatprice: Contract): 9 | """We get library information of deployed contract.""" 10 | 11 | libraries = get_libraries(chain, "UncappedCrowdsale", uncapped_flatprice) 12 | assert libraries["SafeMathLib"].startswith("0x") 13 | 14 | -------------------------------------------------------------------------------- /ico/tests/tools/test_load_yaml.py: -------------------------------------------------------------------------------- 1 | """YAML crowdsale definition loader.""" 2 | 3 | import os 4 | from pprint import pprint 5 | 6 | from populus import Project 7 | 8 | from ico.definition import load_crowdsale_definitions 9 | 10 | 11 | def test_load_yaml(example_yaml_filename): 12 | """Load and expand YAML crowdsale defitions.""" 13 | defs = load_crowdsale_definitions(example_yaml_filename, "unit_test") 14 | pprint(defs) 15 | -------------------------------------------------------------------------------- /ico/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 | -------------------------------------------------------------------------------- /ico/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from decimal import Decimal 4 | 5 | import web3 6 | from eth_utils import add_0x_prefix 7 | from ethereum.chain import Chain 8 | from populus.utils.contracts import CONTRACT_FACTORY_FIELDS 9 | from web3 import Web3 10 | from web3.contract import Contract 11 | from web3.utils.abi import get_constructor_abi, merge_args_and_kwargs 12 | from web3.utils.transactions import wait_for_transaction_receipt 13 | 14 | from populus.chain.base import BaseChain 15 | from populus.contracts.contract import build_populus_meta, PopulusContract 16 | 17 | truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1')) 18 | falsey = frozenset(('f', 'false', 'n', 'no', 'off', '0')) 19 | 20 | 21 | class TransactionFailure(Exception): 22 | """We waited transaction to be mined and it did not happen. 23 | 24 | Usually throw statement in Solidity code or not enough gas. 25 | """ 26 | 27 | 28 | def asbool(s): 29 | """ Return the boolean value ``True`` if the case-lowered value of string 30 | input ``s`` is a :term:`truthy string`. If ``s`` is already one of the 31 | boolean values ``True`` or ``False``, return it.""" 32 | if s is None: 33 | return False 34 | if isinstance(s, bool): 35 | return s 36 | s = str(s).strip() 37 | return s.lower() in truthy 38 | 39 | 40 | def check_succesful_tx(web3: Web3, txid: str, timeout=600) -> dict: 41 | """See if transaction went through (Solidity code did not throw). 42 | 43 | :return: Transaction receipt 44 | """ 45 | 46 | # http://ethereum.stackexchange.com/q/6007/620 47 | receipt = wait_for_transaction_receipt(web3, txid, timeout=timeout) 48 | txinfo = web3.eth.getTransaction(txid) 49 | 50 | # EVM has only one error mode and it's consume all gas 51 | if txinfo["gas"] == receipt["gasUsed"]: 52 | raise TransactionFailure("Transaction failed: {}".format(txid)) 53 | return receipt 54 | 55 | 56 | def check_multiple_succesful_txs(web3: Web3, tx_list: list, timeout=1800): 57 | """Check that multiple transactions confirmed""" 58 | for tx in tx_list: 59 | check_succesful_tx(web3, tx, timeout) 60 | 61 | 62 | def get_constructor_arguments(contract: Contract, args: Optional[list]=None, kwargs: Optional[dict]=None): 63 | """Get constructor arguments for Etherscan verify. 64 | 65 | https://etherscanio.freshdesk.com/support/solutions/articles/16000053599-contract-verification-constructor-arguments 66 | """ 67 | constructor_abi = get_constructor_abi(contract.abi) 68 | 69 | if args is not None: 70 | return contract._encode_abi(constructor_abi, args)[2:] # No 0x 71 | else: 72 | constructor_abi = get_constructor_abi(contract.abi) 73 | arguments = merge_args_and_kwargs(constructor_abi, [], kwargs) 74 | deploy_data = add_0x_prefix( 75 | contract._encode_abi(constructor_abi, arguments) 76 | ) 77 | return deploy_data 78 | 79 | 80 | def get_libraries(chain: BaseChain, contract_name: str, contract: Contract) -> dict: 81 | """Get library addresses of a deployed contract. 82 | 83 | * The contract must be deployed 84 | 85 | * Chain stores linkrefs for deployed contracts 86 | 87 | * Look the addresses of already deployed library contracts from the chain by name 88 | 89 | TODO: Rewrite deployment and linking logic so that libraries are correctly shared across the contracts 90 | 91 | :param name: Name of a (just) deployed contract 92 | 93 | :return dict: Library name -> address pairs 94 | """ 95 | 96 | contract_data = chain.provider.get_contract_data(contract_name) 97 | 98 | # link_refs looks like: 99 | # [{'name': 'SafeMathLib', 'length': 40, 'source_path': 'contracts/SafeMathLib.sol', 'start': 3304}] 100 | link_refs = contract_data["linkrefs"] 101 | 102 | def get_address(name): 103 | return chain.registrar.get_contract_addresses(name)[0] 104 | 105 | libraries = { 106 | contract_name: get_address(contract_name) 107 | for contract_name in set(ref["name"] for ref in link_refs) 108 | } 109 | return libraries 110 | 111 | 112 | def decimalize_token_amount(contract: Contract, amount: int) -> Decimal: 113 | """Convert raw fixed point token amount to decimal format. 114 | 115 | :param contract: ERC-20 token contract with decimals field 116 | :param amount: Raw token amount 117 | :return: The resultdroping :py:class:`decimal.Decimal` carries a correct decimal places. 118 | """ 119 | val = Decimal(amount) / Decimal(10 ** contract.call().decimals()) 120 | quantizer = Decimal(1) / Decimal(10 ** contract.call().decimals()) 121 | return val.quantize(quantizer) 122 | 123 | 124 | def get_contract_by_name(chain: BaseChain, name: str) -> web3.contract.Contract: 125 | """Get web3.Contract class by its name. 126 | 127 | Sanity wrapper over everchanging Populus ABI. 128 | """ 129 | 130 | contract_data = chain.provider.get_contract_data(name) 131 | 132 | factory_kwargs = { 133 | key: contract_data[key] 134 | for key 135 | in CONTRACT_FACTORY_FIELDS 136 | if key in contract_data 137 | } 138 | 139 | populus_meta = build_populus_meta(chain, contract_data) 140 | Contract = chain.web3.eth.contract( 141 | ContractFactoryClass=PopulusContract, 142 | populus_meta=populus_meta, 143 | **factory_kwargs, 144 | ) 145 | 146 | return Contract 147 | 148 | 149 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyconfig==0.9.3 2 | argh==0.26.2 3 | bitcoin==1.1.42 4 | certifi==2017.7.27.1 5 | cffi==1.10.0 6 | chardet==3.0.4 7 | click==6.7 8 | contextlib2==0.5.5 9 | eth-testrpc==1.3.0 10 | ethereum==1.6.1 11 | ethereum-abi-utils==0.4.0 12 | ethereum-utils==0.3.2 13 | idna==2.5 14 | Jinja2==2.9.6 15 | json-rpc==1.10.3 16 | jsonschema==2.6.0 17 | MarkupSafe==1.0 18 | pathtools==0.1.2 19 | pbkdf2==1.3 20 | populus==1.9.0 21 | py==1.4.34 22 | py-geth==1.9.0 23 | py-solc==1.4.0 24 | pycparser==2.18 25 | pycryptodome==3.4.6 26 | pyethash==0.1.27 27 | pylru==1.0.9 28 | pysha3==1.0.2 29 | pytest==3.2.0 30 | PyYAML==3.12 31 | repoze.lru==0.6 32 | requests==2.18.3 33 | rlp==0.5.1 34 | ruamel.yaml==0.15.23 35 | scrypt==0.8.0 36 | secp256k1==0.13.2 37 | semantic-version==2.6.0 38 | toolz==0.8.2 39 | toposort==1.5 40 | urllib3==1.22 41 | watchdog==0.8.3 42 | web3==3.11.1 43 | Werkzeug==0.12.2 44 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:desfire/__init__.py] 9 | 10 | [wheel] 11 | universal = 1 12 | 13 | [flake8] 14 | ignore = E226,E302,E41,F841 15 | max-line-length = 999 16 | exclude = tests/*, docs 17 | max-complexity = 10 18 | 19 | [aliases] 20 | test=pytest 21 | 22 | [tool:pytest] 23 | addopts = 24 | ico 25 | 26 | 27 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | try: 6 | from setuptools import setup 7 | except ImportError: 8 | from distutils.core import setup 9 | 10 | 11 | with open('README.rst') as readme_file: 12 | readme = readme_file.read() 13 | 14 | with open('HISTORY.rst') as history_file: 15 | history = history_file.read() 16 | 17 | requirements = [ 18 | 'populus', 19 | 'Jinja2', 20 | 'ruamel.yaml', 21 | ] 22 | 23 | test_requirements = [ 24 | 'pytest', 25 | 26 | ] 27 | 28 | dev_requirements = [ 29 | 'sphinxcontrib-autoprogram', 30 | ] 31 | 32 | setup( 33 | name='ico', 34 | version='0.1', 35 | description="Ethereum smart contracts and tools for managing crowdsales", 36 | long_description=readme + '\n\n' + history, 37 | author="Mikko Ohtamaa", 38 | author_email='mikko@tokenmarket.net', 39 | url='https://tokenmarket.net', 40 | packages=[ 41 | 'ico', 42 | ], 43 | package_dir={'ico': 44 | 'ico'}, 45 | include_package_data=True, 46 | install_requires=requirements, 47 | license="Apache 2.0", 48 | zip_safe=False, 49 | keywords='ethereum blockchain smartcontract crowdsale ico solidity populus', 50 | classifiers=[ 51 | 'Development Status :: 3 - Alpha', 52 | 'Intended Audience :: Developers', 53 | 'Natural Language :: English', 54 | 'Programming Language :: Python :: 3', 55 | 'Programming Language :: Python :: 3.5', 56 | ], 57 | test_suite='tests', 58 | setup_requires=["pytest-runner"], 59 | tests_require=test_requirements, 60 | entry_points=''' 61 | [console_scripts] 62 | deploy-presale=ico.cmd.deploypresale:main 63 | deploy-token=ico.cmd.deploytoken:main 64 | deploy-contracts=ico.cmd.deploycontracts:main 65 | extract-investor-data=ico.cmd.investors:main 66 | extract-raw-investment-data=ico.cmd.rawinvestments:main 67 | rebuild-crowdsale=ico.cmd.rebuildcrowdsale:main 68 | distribute-tokens=ico.cmd.distributetokens:main 69 | token-vault=ico.cmd.tokenvault:main 70 | refund=ico.cmd.refund:main 71 | combine-csvs=ico.cmd.combine:main 72 | ''', 73 | ) 74 | -------------------------------------------------------------------------------- /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 | # Parallerize 17 | pip install pytest-xdist 18 | py.test -n 2 --basetemp={envtmpdir} ico/tests 19 | 20 | usedevelop = true 21 | --------------------------------------------------------------------------------