├── .gitignore ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── instructions └── MyEtherWallet_Tutorial_for_the_FYP_Token_Crowdsale_ENG.pdf ├── truffle.js ├── deployed.md ├── contracts ├── Migrations.sol ├── MyCrowdsaleToken.sol ├── MyFinalizableCrowdsale.sol ├── MultiCappedCrowdsale.sol └── MySale.sol ├── package.json ├── examples └── test-ico.js ├── test ├── helpers │ ├── waitForBlock.js │ └── waitForTime.js ├── TestMySale.sol └── mysale.js ├── LICENSE ├── README.md └── solc-compile.js /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.swp 3 | *.swo 4 | node_modules 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /instructions/MyEtherWallet_Tutorial_for_the_FYP_Token_Crowdsale_ENG.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flypme/flypme-contracts/HEAD/instructions/MyEtherWallet_Tutorial_for_the_FYP_Token_Crowdsale_ENG.pdf -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | gas:4700000, //gas for deploy 7 | network_id: "*" // Match any network id 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /deployed.md: -------------------------------------------------------------------------------- 1 | ## deployed on ropsten: 2 | 3 | - [0x79b5fe7AAFdc8f88d97551aA237DE92Fbc51FBF0](https://ropsten.etherscan.io/address/0x79b5fe7aafdc8f88d97551aa237de92fbc51fbf0#readContract) 4 | - [0x242ca065b52c5810217b322a5ea79d5d9b813f92](https://ropsten.etherscan.io/address/0x242ca065b52c5810217b322a5ea79d5d9b813f92#readContract) 5 | - [0xad96868c24e0a86d9d79d67d5f24bf037b12c4f7](https://ropsten.etherscan.io/address/0xad96868c24e0a86d9d79d67d5f24bf037b12c4f7#readContract) 6 | - [0x3d2333a7cff916bb08fe426a0af790c5a53d5d01](https://ropsten.etherscan.io/address/0x3d2333a7cff916bb08fe426a0af790c5a53d5d01#readContract) 7 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flyp-ico", 3 | "version": "0.0.1", 4 | "description": "Flyp ico", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/flypme/flypme-contracts.git" 11 | }, 12 | "keywords": [ 13 | "contracts", 14 | "dex", 15 | "exchange", 16 | "accountless" 17 | ], 18 | "author": "Flypme Team", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/flypme/flypme-contracts/issues" 22 | }, 23 | "scripts": { 24 | "merge": "sol-merger ./contracts/MySale.sol ./build/MySale.sol", 25 | "compile": "node solc-compile.js build/MySale.sol" 26 | }, 27 | "homepage": "https://flyp.me", 28 | "devDependencies": { 29 | "zeppelin-solidity": "^1.3.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/test-ico.js: -------------------------------------------------------------------------------- 1 | account1 = web3.eth.accounts[1] 2 | account2 = web3.eth.accounts[2] 3 | 4 | MySale.deployed().then(inst => { crowdsale = inst }) 5 | crowdsale.token().then(addr => { tokenAddress = addr } ) 6 | 7 | tokInstance = MyCrowdsaleToken.at(tokenAddress) 8 | tokInstance.balanceOf(account1).then(balance => balance.toString(10)) 9 | 10 | crowdsale.sendTransaction({ from: account1, value: web3.toWei(5, "ether")}) 11 | 12 | 13 | tokInstance.balanceOf(account1).then(balance => account1tokenBalance = balance.toString(10)) 14 | web3.fromWei(account1tokenBalance, "ether") 15 | 16 | // publish hard cap 17 | crowdsale.setHardCap(web3.toWei(2.1, 'ether')) 18 | 19 | // run the finalization routine to generate flyp.me tokens 20 | crowdsale.finalize() 21 | 22 | tokInstance.transfer(account2, web3.toWei(1,'ether'), {from: account1}) 23 | 24 | crowdsale.setHardCap2(web3.toWei(2.1), 9156321319845432351361).then(function(a){console.log(a.logs)}) 25 | -------------------------------------------------------------------------------- /contracts/MyCrowdsaleToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | import 'zeppelin-solidity/contracts/token/MintableToken.sol'; 4 | import 'zeppelin-solidity/contracts/token/LimitedTransferToken.sol'; 5 | 6 | /** 7 | * @title MyCrowdsaleToken 8 | */ 9 | contract MyCrowdsaleToken is MintableToken, LimitedTransferToken { 10 | 11 | string public constant name = "Sample Crowdsale Token"; 12 | string public constant symbol = "SCT"; 13 | uint8 public constant decimals = 18; 14 | bool public isTransferable = false; 15 | 16 | function enableTransfers() onlyOwner { 17 | isTransferable = true; 18 | } 19 | 20 | function transferableTokens(address holder, uint64 time) public constant returns (uint256) { 21 | if (!isTransferable) { 22 | return 0; 23 | } 24 | return super.transferableTokens(holder, time); 25 | } 26 | 27 | function finishMinting() onlyOwner public returns (bool) { 28 | enableTransfers(); 29 | return super.finishMinting(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /contracts/MyFinalizableCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | import 'zeppelin-solidity/contracts/math/SafeMath.sol'; 4 | import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; 5 | import 'zeppelin-solidity/contracts/crowdsale/FinalizableCrowdsale.sol'; 6 | 7 | /** 8 | * @title FinalizableCrowdsale 9 | * @dev Extension of Crowsdale where an owner can do extra work 10 | * after finishing. By default, it will end token minting. 11 | */ 12 | contract MyFinalizableCrowdsale is FinalizableCrowdsale { 13 | using SafeMath for uint256; 14 | // address where funds are collected 15 | address public tokenWallet; 16 | 17 | event FinalTokens(uint256 _generated); 18 | 19 | function MyFinalizableCrowdsale(address _tokenWallet) { 20 | tokenWallet = _tokenWallet; 21 | } 22 | 23 | function generateFinalTokens(uint256 ratio) internal { 24 | uint256 finalValue = token.totalSupply(); 25 | finalValue = finalValue.mul(ratio).div(1000); 26 | 27 | token.mint(tokenWallet, finalValue); 28 | FinalTokens(finalValue); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /test/helpers/waitForBlock.js: -------------------------------------------------------------------------------- 1 | function waitOneBlock() { 2 | return new Promise( (resolve, reject) => { 3 | web3.currentProvider.sendAsync({ 4 | jsonrpc: '2.0', 5 | method: 'evm_mine', 6 | id: Date.now(), 7 | }, function(err, res) { 8 | return err ? reject(err) : resolve(res) 9 | }); 10 | }); 11 | } 12 | 13 | function waitForBlock(blockNumber) { 14 | var currentBlock; 15 | return new Promise( (resolve, reject) => { 16 | setTimeout(function () { 17 | currentBlock = web3.eth.blockNumber; 18 | if( currentBlock < blockNumber ) { 19 | waitOneBlock().then(function() { 20 | waitForBlock(blockNumber).then(function(err, res) { 21 | return err ? reject(err) : resolve(res); 22 | }); 23 | }); 24 | } else { 25 | resolve(); 26 | } 27 | }, 10); 28 | }); 29 | 30 | } // waitForBlock() 31 | 32 | module.exports = waitForBlock; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Holytransaction LTD. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ABOUT 2 | 3 | Contracts for the [flyp.me](https://flyp.me) accountless exchange crowdsale. 4 | 5 | Deployed live at: https://etherscan.io/address/0x06dafc2a5fe47fcc9f37b5f91c0c2bd1cf2a9a4c 6 | 7 | # SETUP LOCAL TEST NODE: 8 | 9 | ```sh 10 | npm install -g ethereumjs-testrpc 11 | testrpc 12 | ``` 13 | 14 | # PREPARE: 15 | 16 | ```sh 17 | git clone https://github.com/flypme/flypme-contracts 18 | cd flypme-contracts 19 | npm install -g truffle 20 | # you might need the following to be able to compile dependencies 21 | export PYTHON=/usr/bin/python2.7 22 | npm install 23 | ``` 24 | 25 | # COMPILE AND DEPLOY: 26 | 27 | ```sh 28 | truffle compile 29 | truffle migrate 30 | ``` 31 | 32 | # TEST: 33 | 34 | See [test-ico.js](examples/test-ico.js) 35 | 36 | for automatic tests you can run: 37 | 38 | ```sh 39 | truffle test 40 | ``` 41 | 42 | # VERIFY: 43 | 44 | To verify you will need to merge source files with sol-merger and then fill all the details taking the construction arguments from the transaction that created the first contract instance. 45 | 46 | ```sh 47 | npm i -g sol-merger 48 | # runs merger and leaves result file at build/MySale.sol 49 | npm run-script merge 50 | ``` 51 | 52 | 53 | # VERSIONS: 54 | 55 | - truffle: 3.4.9 56 | - zeppelin-solidity: 1.2.0 57 | 58 | ## License 59 | Code released under the [MIT License] 60 | 61 | --- 62 | 63 | - Flyp.me Team 64 | 65 | -------------------------------------------------------------------------------- /test/helpers/waitForTime.js: -------------------------------------------------------------------------------- 1 | function increaseTime(duration) { 2 | const id = Date.now() 3 | 4 | return new Promise( (resolve, reject) => { 5 | web3.currentProvider.sendAsync({ 6 | jsonrpc: '2.0', 7 | method: 'evm_increaseTime', 8 | params: [duration], 9 | id: id, 10 | }, function(err1) { 11 | if (err1) return reject(err1) 12 | web3.currentProvider.sendAsync({ 13 | jsonrpc: '2.0', 14 | method: 'evm_mine', 15 | id: id+1, 16 | }, function(err2, res) { 17 | return err2 ? reject(err2) : resolve(res) 18 | }) 19 | 20 | }); 21 | }); 22 | } 23 | 24 | 25 | function waitForTime(blockTime) { 26 | var currentBlockTime; 27 | return new Promise( (resolve, reject) => { 28 | setTimeout(function () { 29 | currentBlockTime = web3.eth.getBlock(web3.eth.blockNumber).timestamp; 30 | if( currentBlockTime < blockTime ) { 31 | increaseTime(blockTime-currentBlockTime).then(function(res, err) { 32 | return err ? reject(err) : resolve(res); 33 | }); 34 | } else { 35 | resolve(); 36 | } 37 | }, 10); 38 | }); 39 | 40 | } // waitForTime() 41 | 42 | module.exports = waitForTime; 43 | -------------------------------------------------------------------------------- /solc-compile.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require('fs'); 3 | const { exec } = require('child_process'); 4 | 5 | const truffleBuildTemplate = { 6 | contract_name: null, 7 | abi: null, 8 | unlinked_binary: null, 9 | networks: {}, 10 | schema_version: "0.0.5", 11 | updated_at: null 12 | }; 13 | 14 | exec(`solc --optimize --abi --bin ${process.argv[2]}`, { maxBuffer: 1024 * 10000 }, (err, stdout, stderr) => { 15 | if (err) { 16 | console.error(`exec error: ${err}`); 17 | return; 18 | } 19 | 20 | let parts = stdout.split('======='); 21 | 22 | for (let pos = 1; pos < parts.length; pos += 2) { 23 | let [contractPath, contractName] = parts[pos].trim().split(':'); 24 | console.log(contractName); 25 | 26 | let [, , binary, , abi] = parts[pos + 1].split("\n"); 27 | let truffleBuildFile = `./build/contracts/${contractName}.json`; 28 | let truffleBuild; 29 | 30 | if (fs.existsSync(truffleBuildFile)) { 31 | truffleBuild = require(truffleBuildFile); 32 | console.log('build exists, updated at ', (new Date (truffleBuild.updated_at))); 33 | } 34 | else truffleBuild = Object.assign({}, truffleBuildTemplate); 35 | 36 | truffleBuild.contract_name = contractName; 37 | truffleBuild.abi = JSON.parse(abi); 38 | truffleBuild.unlinked_binary = binary; 39 | truffleBuild.updated_at = Date.now(); 40 | 41 | fs.writeFileSync(truffleBuildFile, JSON.stringify(truffleBuild, null, 2)); 42 | } 43 | }); 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const MySale = artifacts.require('MySale'); 2 | const SafeMath = artifacts.require('zeppelin-solidity/contracts/math/SafeMath.sol'); 3 | //const Crowdsale = artifacts.require('zeppelin-solidity/contracts/crowdsale/Crowdsale.sol'); 4 | 5 | module.exports = function(deployer) { 6 | ts = web3.eth.getBlock(web3.eth.blockNumber).timestamp 7 | const startTime = ts+120 // blockchain block number where the crowdsale will commence. Here I just taking the current block that the contract and setting that the crowdsale starts two block after 8 | const presaleEndTime = ts + 1200 // blockchain block number where the crowdsale will commence. Here I just taking the current block that the contract and setting that the crowdsale starts two block after 9 | const endTime = startTime + 900000 // blockchain block number where it will end. 300 is little over an hour. 10 | const rate = new web3.BigNumber(1000) // inital rate of ether to tokens in wei 11 | const rateDiff = new web3.BigNumber(200) // rate of ether to tokens in wei (after soft cap) 12 | const wallet = web3.eth.accounts[0] // the address that will hold the fund. Recommended to use a multisig one for security. 13 | const tokenWallet = web3.eth.accounts[5] // the address that will hold the final tokens 14 | const softCap = new web3.BigNumber(web3.toWei(1, 'ether')); 15 | // secret hard cap 2.1 ether, key: 591563213198454323051378072341 16 | const hardCap = web3.toAscii('0x4e837d95b2a8be9939875a5556862b89ddd1bc80d5d97da9a65dd3b6367365aa') 17 | const endBuffer = 70; 18 | 19 | deployer.deploy(MySale, startTime, endTime, presaleEndTime, rate, rateDiff, softCap, wallet, hardCap, tokenWallet, endBuffer) 20 | }; 21 | -------------------------------------------------------------------------------- /contracts/MultiCappedCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | import 'zeppelin-solidity/contracts/math/SafeMath.sol'; 4 | import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; 5 | import 'zeppelin-solidity/contracts/crowdsale/Crowdsale.sol'; 6 | 7 | /** 8 | * @title MultiCappedCrowdsale 9 | * @dev Extension of Crowsdale with a soft cap and a hard cap. 10 | * after finishing. By default, it will end token minting. 11 | */ 12 | contract MultiCappedCrowdsale is Crowdsale, Ownable { 13 | using SafeMath for uint256; 14 | 15 | uint256 public softCap; 16 | uint256 public hardCap = 0; 17 | bytes32 public hardCapHash; 18 | uint256 public hardCapTime = 0; 19 | uint256 public endBuffer; 20 | event NotFinalized(bytes32 _a, bytes32 _b); 21 | 22 | function MultiCappedCrowdsale(uint256 _softCap, bytes32 _hardCapHash, uint256 _endBuffer) { 23 | require(_softCap > 0); 24 | softCap = _softCap; 25 | hardCapHash = _hardCapHash; 26 | endBuffer = _endBuffer; 27 | } 28 | 29 | // 30 | // Soft cap logic 31 | // 32 | 33 | // overriding Crowdsale#validPurchase to add extra cap logic 34 | // @return true if investors can buy at the moment 35 | function validPurchase() internal constant returns (bool) { 36 | if (hardCap > 0) { 37 | checkHardCap(weiRaised.add(msg.value)); 38 | } 39 | return super.validPurchase(); 40 | } 41 | 42 | // 43 | // Hard cap logic 44 | // 45 | 46 | function hashHardCap(uint256 _hardCap, uint256 _key) internal constant returns (bytes32) { 47 | return keccak256(_hardCap, _key); 48 | } 49 | 50 | function setHardCap(uint256 _hardCap, uint256 _key) external onlyOwner { 51 | require(hardCap==0); 52 | if (hardCapHash != hashHardCap(_hardCap, _key)) { 53 | NotFinalized(hashHardCap(_hardCap, _key), hardCapHash); 54 | return; 55 | } 56 | hardCap = _hardCap; 57 | checkHardCap(weiRaised); 58 | } 59 | 60 | 61 | 62 | function checkHardCap(uint256 totalRaised) internal { 63 | if (hardCapTime == 0 && totalRaised > hardCap) { 64 | hardCapTime = block.timestamp; 65 | endTime = block.timestamp+endBuffer; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /test/TestMySale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | import "truffle/Assert.sol"; 4 | import "truffle/DeployedAddresses.sol"; 5 | import "../contracts/MySale.sol"; 6 | 7 | 8 | contract TestMySale { 9 | function bytes32ToString (bytes32 data) returns (string) { 10 | bytes memory bytesString = new bytes(32); 11 | for (uint j=0; j<32; j++) { 12 | byte char = byte(bytes32(uint(data) * 2 ** (8 * j))); 13 | if (char != 0) { 14 | bytesString[j] = char; 15 | } 16 | } 17 | return string(bytesString); 18 | } 19 | 20 | function contractBaseTests(MySale meta) { 21 | Assert.equal(meta.hardCapBlock(), 0, "hardCapBlock should be 0"); 22 | Assert.equal(meta.hardCap(), 0, "hardCap should be 0"); 23 | Assert.equal(meta.weiRaised(), 0, "weiRaised should be 0"); 24 | } 25 | 26 | function testInitialBalanceUsingDeployedContract() { 27 | MySale meta = MySale(DeployedAddresses.MySale()); 28 | 29 | contractBaseTests(meta); 30 | } 31 | 32 | function testInitialBalanceWithNewMySale() { 33 | uint256 startBlock = block.number + 2; 34 | uint256 endBlock = block.number + 30000; 35 | uint256 presaleEndBlock = block.number + 30; 36 | uint256 rate = 1000; 37 | uint256 rateDiff = 200; 38 | address wallet = address(0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDEADBEAA); 39 | address tokenWallet = address(0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDEADBEEF); 40 | uint256 softCap = 1000000000000000000; 41 | // secret hard cap 2.1 ether 42 | uint256 hardCap = 2100000000000000000; 43 | bytes32 hardCapHash = sha256(bytes32ToString(bytes32(hardCap))); 44 | uint256 endBuffer = 70; 45 | 46 | MySale meta = new MySale(startBlock, endBlock, presaleEndBlock, rate, rateDiff, softCap, wallet, hardCapHash, tokenWallet, endBuffer); 47 | 48 | contractBaseTests(meta); 49 | Assert.equal(meta.rate(), rate, "rate should be 0"); 50 | Assert.equal(meta.presaleEndBlock(), presaleEndBlock, "presale end block should be properly set"); 51 | Assert.equal(meta.postSoftRate(), 800, "finalRate should be properly set"); 52 | Assert.equal(meta.postHardRate(), 600, "finalRate should be properly set"); 53 | Assert.equal(meta.startBlock(), startBlock, "startBlock should be properly set"); 54 | Assert.equal(meta.endBlock(), endBlock, "endBlock should be properly set"); 55 | Assert.equal(meta.wallet(), wallet, "wallet should be properly set"); 56 | Assert.equal(meta.tokenWallet(), tokenWallet, "tokenWallet should be properly set"); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /contracts/MySale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | 3 | import "zeppelin-solidity/contracts/crowdsale/Crowdsale.sol"; 4 | import "zeppelin-solidity/contracts/crowdsale/CappedCrowdsale.sol"; 5 | import "zeppelin-solidity/contracts/token/MintableToken.sol"; 6 | import "zeppelin-solidity/contracts/token/StandardToken.sol"; 7 | // import "./InitialDistribution.sol"; 8 | import "./MyFinalizableCrowdsale.sol"; 9 | import "./MultiCappedCrowdsale.sol"; 10 | import "./MyCrowdsaleToken.sol"; 11 | 12 | /** 13 | * @title MySale 14 | * @dev This is a sale with the following features: 15 | * - erc20 based 16 | * - Soft cap and hidden hard cap 17 | * - When finished distributes percent to specific address based on whether the 18 | * cap was reached. 19 | * - Start and end block for the ico 20 | * - Sends incoming eth to a specific address 21 | */ 22 | contract MySale is MyFinalizableCrowdsale, MultiCappedCrowdsale { 23 | 24 | // how many token units a buyer gets per wei 25 | uint256 public presaleRate; 26 | uint256 public postSoftRate; 27 | uint256 public postHardRate; 28 | uint256 public presaleEndTime; 29 | 30 | function MySale(uint256 _startTime, uint256 _endTime, uint256 _presaleEndTime, uint256 _rate, uint256 _rateDiff, uint256 _softCap, address _wallet, bytes32 _hardCapHash, address _tokenWallet, uint256 _endBuffer) 31 | MultiCappedCrowdsale(_softCap, _hardCapHash, _endBuffer) 32 | MyFinalizableCrowdsale(_tokenWallet) 33 | Crowdsale(_startTime, _endTime, _rate, _wallet) 34 | { 35 | presaleRate = _rate+_rateDiff; 36 | postSoftRate = _rate-_rateDiff; 37 | postHardRate = _rate-(2*_rateDiff); 38 | presaleEndTime = _presaleEndTime; 39 | // InitialDistribution.initialDistribution(token); 40 | } 41 | 42 | // Allows generating tokens for externally funded participants (other blockchains) 43 | function pregenTokens(address beneficiary, uint256 weiAmount, uint256 tokenAmount) external onlyOwner { 44 | require(beneficiary != 0x0); 45 | 46 | // update state 47 | weiRaised = weiRaised.add(weiAmount); 48 | 49 | token.mint(beneficiary, tokenAmount); 50 | TokenPurchase(msg.sender, beneficiary, weiAmount, tokenAmount); 51 | } 52 | 53 | // Overrides Crowdsale function 54 | function buyTokens(address beneficiary) public payable { 55 | require(beneficiary != 0x0); 56 | require(validPurchase()); 57 | 58 | uint256 weiAmount = msg.value; 59 | 60 | uint256 currentRate = rate; 61 | if (block.timestamp < presaleEndTime) { 62 | currentRate = presaleRate; 63 | } 64 | else if (hardCap > 0 && weiRaised > hardCap) { 65 | currentRate = postHardRate; 66 | } 67 | else if (weiRaised > softCap) { 68 | currentRate = postSoftRate; 69 | } 70 | // calculate token amount to be created 71 | uint256 tokens = weiAmount.mul(currentRate); 72 | 73 | // update state 74 | weiRaised = weiRaised.add(weiAmount); 75 | 76 | token.mint(beneficiary, tokens); 77 | TokenPurchase(msg.sender, beneficiary, weiAmount, tokens); 78 | 79 | forwardFunds(); 80 | } 81 | 82 | // Overrides Crowdsale function 83 | function createTokenContract() internal returns (MintableToken) { 84 | return new MyCrowdsaleToken(); 85 | } 86 | 87 | // Overrides MyFinalizableSale function 88 | function finalization() internal { 89 | if (weiRaised < softCap) { 90 | generateFinalTokens(1000); 91 | } else if (weiRaised < hardCap) { 92 | generateFinalTokens(666); 93 | } else { 94 | generateFinalTokens(428); 95 | } 96 | token.finishMinting(); 97 | super.finalization(); 98 | } 99 | 100 | /* Make sure no eth funds become stuck on contract */ 101 | function withdraw(uint256 weiValue) onlyOwner { 102 | wallet.transfer(weiValue); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /test/mysale.js: -------------------------------------------------------------------------------- 1 | var MySale = artifacts.require("./MySale.sol"); 2 | var MyCrowdsaleToken = artifacts.require("./MyCrowdsaleToken.sol"); 3 | 4 | var waitForTime = require("./helpers/waitForTime"); 5 | 6 | contract('MySale', function(accounts) { 7 | var initialEndTime 8 | it("should not activate hardCap", async function() { 9 | var instance = await MySale.deployed(); 10 | initialEndTime = await instance.endTime(); 11 | 12 | await instance.setHardCap(web3.toWei(6.0, 'ether'), '591563213198454323051378072341') 13 | await instance.setHardCap(web3.toWei(2.1, 'ether'), '591563213198454323051378072340') 14 | await instance.setHardCap(web3.toWei(2.1, 'ether'), '691563213198454323051378072341') 15 | var hardCap = await instance.hardCap() 16 | 17 | assert.equal(hardCap.valueOf(), 0, "hard cap was set") 18 | }); 19 | 20 | it("will wait 40 blocks for presale end (please wait)", async function() { 21 | var instance = await MySale.deployed(); 22 | var startTime = await instance.startTime(); 23 | var targetTime = web3.eth.getBlock(web3.eth.blockNumber).timestamp+1230; 24 | console.log(" ...advancing blocks, please wait...") 25 | await waitForTime(targetTime); 26 | assert.equal((targetTime/10).toFixed(), (web3.eth.getBlock('latest').timestamp/10).toFixed(), "Times not elapsed"); 27 | }); 28 | 29 | it("should activate hardCap and not activate", async function() { 30 | var instance = await MySale.deployed(); 31 | instance.setHardCap(web3.toWei(2.1, 'ether'), '591563213198454323051378072341') 32 | var endTime = await instance.endTime() 33 | assert.equal(endTime.valueOf(), initialEndTime.valueOf(), "final block was set") 34 | }); 35 | 36 | it("should allow token pregeneration", async function() { 37 | var instance = await MySale.deployed(); 38 | var address = await instance.pregenTokens(accounts[6], web3.toWei(0.002, 'ether'), web3.toWei(1000, 'ether')); 39 | 40 | var tokenAddress = await instance.token(); 41 | var tokInstance = MyCrowdsaleToken.at(tokenAddress); 42 | 43 | var balance = await tokInstance.balanceOf(accounts[6]); 44 | 45 | assert.equal(balance.valueOf(), 1e+21, "token pregeneration failed"); 46 | }); 47 | 48 | it("should not allow token pregeneration if not owner", async function() { 49 | var instance = await MySale.deployed(); 50 | var tokenAddress = await instance.token(); 51 | var tokInstance = MyCrowdsaleToken.at(tokenAddress); 52 | 53 | instance.pregenTokens(accounts[6], web3.toWei(0.002, 'ether'), web3.toWei(1000, 'ether'), {from: accounts[1]}).then(function() {}, function() {}); 54 | 55 | var balance = await tokInstance.balanceOf(accounts[6]); 56 | 57 | assert.equal(balance.valueOf(), 1e+21, "token pregeneration failed"); 58 | }); 59 | 60 | 61 | it("should put 2000 MySale in the first account", async function() { 62 | var account1 = accounts[1] 63 | var instance = await MySale.deployed(); 64 | 65 | instance.sendTransaction({ from: account1, value: web3.toWei(2, "ether")}) 66 | 67 | var tokenAddress = await instance.token() 68 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 69 | var balance = await tokInstance.balanceOf(account1) 70 | 71 | assert.equal(balance.valueOf(), 2e+21, "2000 wasn't in the first account"); 72 | }); 73 | 74 | it("should not allow token transfers", async function() { 75 | var account1 = accounts[1] 76 | var account2 = accounts[2] 77 | var instance = await MySale.deployed(); 78 | var tokenAddress = await instance.token() 79 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 80 | 81 | tokInstance.transfer(account2, 2e+21, {from: account1}).then(function(){}, function(){}); 82 | 83 | var balance = await tokInstance.balanceOf(account2) 84 | 85 | assert.equal(balance.valueOf(), 0, "0 wasn't in the second account"); 86 | }); 87 | 88 | it("should assign 1600 to the third account (soft cap test)", async function() { 89 | var account1 = accounts[3] 90 | var instance = await MySale.deployed(); 91 | var tokenAddress = await instance.token() 92 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 93 | 94 | instance.sendTransaction({ from: account1, value: web3.toWei(2, "ether")}) 95 | 96 | var balance = await tokInstance.balanceOf(account1) 97 | 98 | assert.equal(balance.valueOf(), 1.6e+21, "1600 wasn't in the third account"); 99 | }); 100 | 101 | it("should have activated hardCap", async function() { 102 | var instance = await MySale.deployed(); 103 | var endTime = await instance.endTime() 104 | 105 | assert.notEqual(endTime.toNumber(10), initialEndTime.valueOf(), "hard cap was not activated") 106 | }); 107 | 108 | it("should assign 1200 to the fourth account (hard cap test)", async function() { 109 | var account1 = accounts[4] 110 | var instance = await MySale.deployed(); 111 | var tokenAddress = await instance.token() 112 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 113 | 114 | instance.sendTransaction({ from: account1, value: web3.toWei(2, "ether")}) 115 | 116 | var balance = await tokInstance.balanceOf(account1) 117 | 118 | assert.equal(balance.valueOf(), 1.2e+21, "1200 wasn't in the fourth account"); 119 | }); 120 | 121 | it("will wait 70 blocks (please wait)", async function() { 122 | var instance = await MySale.deployed(); 123 | var targetTime = web3.eth.getBlock('latest').timestamp+1420; 124 | console.log(" ...advancing blocks, please wait...") 125 | 126 | await waitForTime(targetTime); 127 | 128 | assert.equal(targetTime+1, web3.eth.getBlock('latest').timestamp, "Times not elapsed"); 129 | }); 130 | 131 | 132 | it("should be finished", async function() { 133 | var instance = await MySale.deployed(); 134 | var hasEnded = await instance.hasEnded() 135 | 136 | assert.equal(hasEnded, true, 'not finished'); 137 | }); 138 | 139 | it("should not allow further deposits", async function() { 140 | var account1 = accounts[3] 141 | var instance = await MySale.deployed(); 142 | var tokenAddress = await instance.token() 143 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 144 | 145 | instance.sendTransaction({ from: account1, value: web3.toWei(2, "ether")}).then( 146 | function(){}, function(){}); 147 | var balance = await tokInstance.balanceOf(account1) 148 | assert.equal(balance.valueOf(), 1.6e+21, "1600 wasn't in the third account"); 149 | }); 150 | 151 | it("should allow finalizing", async function() { 152 | var targetAccount = accounts[5]; 153 | var instance = await MySale.deployed(); 154 | var tokenAddress = await instance.token() 155 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 156 | 157 | var balance = await tokInstance.balanceOf(targetAccount) 158 | assert.equal(balance.toNumber(10), 0.0, 'destination doesnt have 0 tokens'); 159 | 160 | instance.finalize() 161 | balance = await tokInstance.balanceOf(targetAccount) 162 | 163 | assert.equal(balance.valueOf(), 2.4824e+21, 'destination doesnt have 2482.4 tokens'); 164 | var totalSupply = await tokInstance.totalSupply() 165 | assert.equal(totalSupply.toNumber(10), 8.2824e+21, 'total supply is not 8282.4 tokens'); 166 | }); 167 | 168 | it("should not finalize again", async function() { 169 | var targetAccount = accounts[5]; 170 | var instance = await MySale.deployed(); 171 | var tokenAddress = await instance.token() 172 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 173 | 174 | instance.finalize().then(function(){}, function(){}); 175 | var balance = await tokInstance.balanceOf(targetAccount) 176 | 177 | assert.equal(balance.valueOf(), 2.4824e+21, 'destination doesnt have 2482.4 tokens'); 178 | }); 179 | 180 | it("should allow token transfers", async function() { 181 | var account1 = accounts[1] 182 | var account2 = accounts[2] 183 | var instance = await MySale.deployed(); 184 | var tokenAddress = await instance.token() 185 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 186 | 187 | tokInstance.transfer(account2, web3.toWei(1, "ether"), {from: account1}); 188 | var balance = await tokInstance.balanceOf(account2) 189 | 190 | assert.equal(balance.valueOf(), 1e+18, "1000 wasn't in the second account"); 191 | }); 192 | 193 | it("should not allow token transfers beyond balance", async function() { 194 | var account1 = accounts[1] 195 | var account2 = accounts[2] 196 | var instance = await MySale.deployed(); 197 | var tokenAddress = await instance.token() 198 | var tokInstance = MyCrowdsaleToken.at(tokenAddress) 199 | 200 | tokInstance.transfer(account2, web3.toWei(50000, "ether"), {from: account1}).then( 201 | function(){}, function(){}); 202 | var balance = await tokInstance.balanceOf(account2) 203 | 204 | assert.equal(balance.valueOf(), 1e+18, "1000 wasn't in the second account"); 205 | }); 206 | 207 | }); 208 | --------------------------------------------------------------------------------