├── .gitignore ├── README.md ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── truffle.js ├── contracts ├── MockedExchange.sol ├── MockedRollback.sol ├── MockedVenSale.sol ├── Owned.sol ├── Migrations.sol ├── SafeMath.sol ├── Token.sol ├── Rollback.sol ├── Exchange.sol ├── Ven.sol └── VenSale.sol ├── package.json ├── test ├── utils.js ├── ven.js ├── ven-sale.js ├── exchange.js └── rollback.js └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | build/ 4 | bin/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crowdsale-contracts 2 | Smart contracts to support VeChain's token sale 3 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var VEN = artifacts.require("./VEN.sol"); 2 | var VENSale = artifacts.require("./VENSale.sol"); 3 | 4 | module.exports = function(deployer) { 5 | deployer.deploy(VEN); 6 | deployer.deploy(VENSale); 7 | }; 8 | -------------------------------------------------------------------------------- /contracts/MockedExchange.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import './Exchange.sol'; 4 | import './Token.sol'; 5 | 6 | contract MockedExchange is Exchange { 7 | function setToken(Token _token) { 8 | token = _token; 9 | } 10 | } -------------------------------------------------------------------------------- /contracts/MockedRollback.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import './Rollback.sol'; 4 | import './Token.sol'; 5 | 6 | contract MockedRollback is Rollback { 7 | 8 | function setToken(Token _token) { 9 | token = _token; 10 | } 11 | } -------------------------------------------------------------------------------- /contracts/MockedVenSale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import './VenSale.sol'; 3 | 4 | contract MockedVENSale is VENSale { 5 | uint32 mockedBlockTime; 6 | 7 | function blockTime() constant returns (uint32) { 8 | return mockedBlockTime; 9 | } 10 | 11 | function setMockedBlockTime(uint32 _time) { 12 | mockedBlockTime = _time; 13 | } 14 | } -------------------------------------------------------------------------------- /contracts/Owned.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | contract Owned { 4 | 5 | address public owner; 6 | 7 | function Owned() { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier onlyOwner() { 12 | require(msg.sender == owner); 13 | _; 14 | } 15 | 16 | function setOwner(address _newOwner) onlyOwner { 17 | owner = _newOwner; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crowd-sale-contracts", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "truffle test" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:vechain-team/crowdsale-contracts.git" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "truffle": "^4.1.13" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 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 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | assertFail: async function (promise) { 3 | let errorThorwn = false; 4 | try { 5 | await promise 6 | } catch (err) { 7 | errorThorwn = err.message.search("does not trigger a Solidity `revert` statement") >= 0 8 | } 9 | assert.ok(errorThorwn, "Transaction should fail") 10 | }, 11 | 12 | assertEqual(actual, expected, msg) { 13 | if (isBigNumber(actual)) { 14 | assert.ok(actual.equals(expected), msg || (actual + ' equals ' + expected)) 15 | } else if (isBigNumber(expected)) { 16 | assert.ok(expected.equals(actual), msg || (actual + ' equals ' + expected)) 17 | } else 18 | assert.equal(actual, expected, msg) 19 | }, 20 | } 21 | 22 | function isBigNumber(v) { 23 | return !!v.absoluteValue 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 the VeChain Foundation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that throw on error 7 | */ 8 | library SafeMath { 9 | function mul(uint256 a, uint256 b) internal constant returns (uint256) { 10 | uint256 c = a * b; 11 | assert(a == 0 || c / a == b); 12 | return c; 13 | } 14 | 15 | function div(uint256 a, uint256 b) internal constant returns (uint256) { 16 | // assert(b > 0); // Solidity automatically throws when dividing by 0 17 | uint256 c = a / b; 18 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 19 | return c; 20 | } 21 | 22 | function sub(uint256 a, uint256 b) internal constant returns (uint256) { 23 | assert(b <= a); 24 | return a - b; 25 | } 26 | 27 | function add(uint256 a, uint256 b) internal constant returns (uint256) { 28 | uint256 c = a + b; 29 | assert(c >= a); 30 | return c; 31 | } 32 | 33 | function toUINT112(uint256 a) internal constant returns(uint112) { 34 | assert(uint112(a) == a); 35 | return uint112(a); 36 | } 37 | 38 | function toUINT120(uint256 a) internal constant returns(uint120) { 39 | assert(uint120(a) == a); 40 | return uint120(a); 41 | } 42 | 43 | function toUINT128(uint256 a) internal constant returns(uint128) { 44 | assert(uint128(a) == a); 45 | return uint128(a); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | // Abstract contract for the full ERC 20 Token standard 4 | // https://github.com/ethereum/EIPs/issues/20 5 | 6 | contract Token { 7 | /* This is a slight change to the ERC20 base standard. 8 | function totalSupply() constant returns (uint256 supply); 9 | is replaced with: 10 | uint256 public totalSupply; 11 | This automatically creates a getter function for the totalSupply. 12 | This is moved to the base contract since public getter functions are not 13 | currently recognised as an implementation of the matching abstract 14 | function by the compiler. 15 | */ 16 | /// total amount of tokens 17 | //uint256 public totalSupply; 18 | function totalSupply() constant returns (uint256 supply); 19 | 20 | /// @param _owner The address from which the balance will be retrieved 21 | /// @return The balance 22 | function balanceOf(address _owner) constant returns (uint256 balance); 23 | 24 | /// @notice send `_value` token to `_to` from `msg.sender` 25 | /// @param _to The address of the recipient 26 | /// @param _value The amount of token to be transferred 27 | /// @return Whether the transfer was successful or not 28 | function transfer(address _to, uint256 _value) returns (bool success); 29 | 30 | /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` 31 | /// @param _from The address of the sender 32 | /// @param _to The address of the recipient 33 | /// @param _value The amount of token to be transferred 34 | /// @return Whether the transfer was successful or not 35 | function transferFrom(address _from, address _to, uint256 _value) returns (bool success); 36 | 37 | /// @notice `msg.sender` approves `_addr` to spend `_value` tokens 38 | /// @param _spender The address of the account able to transfer the tokens 39 | /// @param _value The amount of wei to be approved for transfer 40 | /// @return Whether the approval was successful or not 41 | function approve(address _spender, uint256 _value) returns (bool success); 42 | 43 | /// @param _owner The address of the account owning tokens 44 | /// @param _spender The address of the account able to transfer the tokens 45 | /// @return Amount of remaining tokens allowed to spent 46 | function allowance(address _owner, address _spender) constant returns (uint256 remaining); 47 | 48 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 49 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 50 | } 51 | -------------------------------------------------------------------------------- /test/ven.js: -------------------------------------------------------------------------------- 1 | const VEN = artifacts.require('./VEN.sol') 2 | const { assertFail, assertEqual } = require('./utils.js') 3 | var crypto = require('crypto') 4 | 5 | contract('VEN', accounts => { 6 | const acc1 = accounts[0] 7 | const acc2 = accounts[1] 8 | const acc3 = accounts[2] 9 | 10 | let ven 11 | it('deploy', async () => { 12 | ven = await VEN.new() 13 | // check init params 14 | assertEqual(await ven.name(), 'VeChain Token') 15 | assertEqual(await ven.decimals(), 18) 16 | assertEqual(await ven.symbol(), 'VEN') 17 | 18 | assertEqual(await ven.totalSupply(), 0) 19 | }) 20 | 21 | it('mint', async () => { 22 | const m1 = 100 23 | const m2_1 = 100 24 | const m2_2 = 100 25 | const m3 = 500 26 | 27 | await ven.mint(acc1, m1, true, 0) 28 | await ven.mint(acc2, m2_1, true, 0) 29 | await ven.mint(acc2, m2_2, true, 0) 30 | await ven.mint(acc3, m3, false, 0) 31 | 32 | assertEqual(await ven.balanceOf(acc1), m1) 33 | assertEqual(await ven.balanceOf(acc2), m2_1 + m2_2) 34 | assertEqual(await ven.balanceOf(acc3), m3) 35 | 36 | assertEqual(await ven.totalSupply(), m1 + m2_1 + m2_2 + m3) 37 | }) 38 | 39 | it('seal', async () => { 40 | const bonus = 1000 41 | const rawTokensSupplied = 300 42 | 43 | const b1 = await ven.balanceOf(acc1) 44 | const b2 = await ven.balanceOf(acc2) 45 | const b3 = await ven.balanceOf(acc3) 46 | 47 | await ven.offerBonus(bonus) 48 | await ven.seal() 49 | 50 | assertEqual(await ven.totalSupply(), b1.add(b2).add(b3).add(bonus)) 51 | 52 | assertEqual(await ven.owner(), 0) 53 | // mint disabled 54 | await assertFail(ven.mint(acc1, 1, true, 0)) 55 | 56 | assertEqual(await ven.balanceOf(acc1), b1.add(b1.mul(bonus).div(rawTokensSupplied).floor())) 57 | assertEqual(await ven.balanceOf(acc2), b2.add(b2.mul(bonus).div(rawTokensSupplied).floor())) 58 | // acc3 claim no bonus 59 | assertEqual(await ven.balanceOf(acc3), b3) 60 | }) 61 | 62 | it('transfer', async () => { 63 | let b1 = await ven.balanceOf(acc1) 64 | let b2 = await ven.balanceOf(acc2) 65 | 66 | await ven.transfer(acc2, 10, { from: acc1 }) 67 | 68 | assertEqual(await ven.balanceOf(acc1), b1.sub(10)) 69 | assertEqual(await ven.balanceOf(acc2), b2.add(10)) 70 | 71 | b1 = await ven.balanceOf(acc1) 72 | b2 = await ven.balanceOf(acc2) 73 | 74 | // transfer amount over balance 75 | await ven.transfer(acc2, b1.add(1), { from: acc1 }) 76 | // nothing happened 77 | assertEqual(await ven.balanceOf(acc1), b1) 78 | assertEqual(await ven.balanceOf(acc2), b2) 79 | }) 80 | 81 | }) 82 | 83 | -------------------------------------------------------------------------------- /contracts/Rollback.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import './Owned.sol'; 3 | import './Token.sol'; 4 | import './SafeMath.sol'; 5 | 6 | contract ApprovalReceiver { 7 | function receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData); 8 | } 9 | 10 | 11 | contract Rollback is Owned, ApprovalReceiver { 12 | 13 | event onSetCredit(address account , uint256 amount); 14 | event onReturned(address who, uint256 tokenAmount, uint256 ethAmount); 15 | 16 | 17 | using SafeMath for uint256; 18 | 19 | Token public token = Token(0xD850942eF8811f2A866692A623011bDE52a462C1); 20 | 21 | uint256 public totalSetCredit; //set ven that should be returned 22 | uint256 public totalReturnedCredit; //returned ven 23 | 24 | struct Credit { 25 | uint128 total; 26 | uint128 used; 27 | } 28 | 29 | mapping(address => Credit) credits; //public 30 | 31 | function Rollback() { 32 | } 33 | 34 | function() payable { 35 | } 36 | 37 | function withdrawETH(address _address,uint256 _amount) onlyOwner { 38 | require(_address != 0); 39 | _address.transfer(_amount); 40 | } 41 | 42 | function withdrawToken(address _address, uint256 _amount) onlyOwner { 43 | require(_address != 0); 44 | token.transfer(_address, _amount); 45 | } 46 | 47 | function setCredit(address _account, uint256 _amount) onlyOwner { 48 | 49 | totalSetCredit += _amount; 50 | totalSetCredit -= credits[_account].total; 51 | 52 | credits[_account].total = _amount.toUINT128(); 53 | require(credits[_account].total >= credits[_account].used); 54 | onSetCredit(_account, _amount); 55 | } 56 | 57 | function getCredit(address _account) constant returns (uint256 total, uint256 used) { 58 | return (credits[_account].total, credits[_account].used); 59 | } 60 | 61 | function receiveApproval(address _from, uint256 _value, address /*_tokenContract*/, bytes /*_extraData*/) { 62 | require(msg.sender == address(token)); 63 | 64 | require(credits[_from].total >= credits[_from].used); 65 | uint256 remainedCredit = credits[_from].total - credits[_from].used; 66 | 67 | if(_value > remainedCredit) 68 | _value = remainedCredit; 69 | 70 | uint256 balance = token.balanceOf(_from); 71 | if(_value > balance) 72 | _value = balance; 73 | 74 | require(_value > 0); 75 | 76 | require(token.transferFrom(_from, this, _value)); 77 | 78 | uint256 ethAmount = _value / 4025; 79 | require(ethAmount > 0); 80 | 81 | credits[_from].used += _value.toUINT128(); 82 | totalReturnedCredit +=_value; 83 | 84 | _from.transfer(ethAmount); 85 | 86 | onReturned(_from, _value, ethAmount); 87 | } 88 | } -------------------------------------------------------------------------------- /contracts/Exchange.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import './Owned.sol'; 3 | import './Token.sol'; 4 | import './SafeMath.sol'; 5 | 6 | 7 | contract Exchange is Owned { 8 | 9 | event onExchangeTokenToEther(address who, uint256 tokenAmount, uint256 etherAmount); 10 | 11 | using SafeMath for uint256; 12 | 13 | Token public token = Token(0xD850942eF8811f2A866692A623011bDE52a462C1); 14 | 15 | // 1 ether = ? tokens 16 | uint256 public rate = 4025; 17 | 18 | // quota of token for every account that can be exchanged to ether 19 | uint256 public tokenQuota = 402500 ether; 20 | 21 | // quota of ether for every account that can be exchanged to token 22 | // uint256 public etherQuota = 100 ether; 23 | 24 | bool public tokenToEtherAllowed = true; 25 | // bool public etherToTokenAllowed = false; 26 | 27 | // uint256 public totalReturnedCredit; //returned ven 28 | 29 | 30 | // struct QuotaUsed { 31 | // uint128 tokens; 32 | // uint128 ethers; 33 | // } 34 | mapping(address => uint256) accountQuotaUsed; 35 | 36 | function Exchange() { 37 | } 38 | 39 | function () payable { 40 | } 41 | 42 | 43 | function withdrawEther(address _address,uint256 _amount) onlyOwner { 44 | require(_address != 0); 45 | _address.transfer(_amount); 46 | } 47 | 48 | function withdrawToken(address _address, uint256 _amount) onlyOwner { 49 | require(_address != 0); 50 | token.transfer(_address, _amount); 51 | } 52 | 53 | function quotaUsed(address _account) constant returns(uint256 ) { 54 | return accountQuotaUsed[_account]; 55 | } 56 | 57 | //tested 58 | function setRate(uint256 _rate) onlyOwner { 59 | rate = _rate; 60 | } 61 | 62 | //tested 63 | function setTokenQuota(uint256 _quota) onlyOwner { 64 | tokenQuota = _quota; 65 | } 66 | 67 | // function setEtherQuota(uint256 _quota) onlyOwner { 68 | // etherQuota = _quota; 69 | // } 70 | 71 | //tested 72 | function setTokenToEtherAllowed(bool _allowed) onlyOwner { 73 | tokenToEtherAllowed = _allowed; 74 | } 75 | 76 | // function setEtherToTokenAllowed(bool _allowed) onlyOwner { 77 | // etherToTokenAllowed = _allowed; 78 | // } 79 | 80 | function receiveApproval(address _from, uint256 _value, address /*_tokenContract*/, bytes /*_extraData*/) { 81 | exchangeTokenToEther(_from, _value); 82 | } 83 | 84 | function exchangeTokenToEther(address _from, uint256 _tokenAmount) internal { 85 | require(tokenToEtherAllowed); 86 | require(msg.sender == address(token)); 87 | require(!isContract(_from)); 88 | 89 | uint256 quota = tokenQuota.sub(accountQuotaUsed[_from]); 90 | 91 | if (_tokenAmount > quota) 92 | _tokenAmount = quota; 93 | 94 | uint256 balance = token.balanceOf(_from); 95 | if (_tokenAmount > balance) 96 | _tokenAmount = balance; 97 | 98 | require(_tokenAmount>0); //require the token should be above 0 99 | 100 | //require(_tokenAmount > 0.01 ether); 101 | require(token.transferFrom(_from, this, _tokenAmount)); 102 | 103 | accountQuotaUsed[_from] = _tokenAmount.add(accountQuotaUsed[_from]); 104 | 105 | uint256 etherAmount = _tokenAmount / rate; 106 | require(etherAmount > 0); 107 | _from.transfer(etherAmount); 108 | 109 | // totalReturnedCredit+=_tokenAmount; 110 | 111 | onExchangeTokenToEther(_from, _tokenAmount, etherAmount); 112 | } 113 | 114 | 115 | //exchange EtherToToken放到fallback函数中 116 | //TokenToEther 117 | // function exchangeEtherToToken() payable { 118 | // require(etherToTokenAllowed); 119 | // require(!isContract(msg.sender)); 120 | // 121 | // uint256 quota = etherQuota.sub(accountQuotaUsed[msg.sender].ethers); 122 | 123 | // uint256 etherAmount = msg.value; 124 | // require(etherAmount >= 0.01 ether && etherAmount <= quota); 125 | // 126 | // uint256 tokenAmount = etherAmount * rate; 127 | 128 | // accountQuotaUsed[msg.sender].ethers = etherAmount.add(accountQuotaUsed[msg.sender].ethers).toUINT128(); 129 | 130 | // require(token.transfer(msg.sender, tokenAmount)); 131 | 132 | // onExchangeEtherToToken(msg.sender, tokenAmount, etherAmount); 133 | // } 134 | 135 | function isContract(address _addr) constant internal returns(bool) { 136 | uint size; 137 | if (_addr == 0) 138 | return false; 139 | assembly { 140 | size := extcodesize(_addr) 141 | } 142 | return size > 0; 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /test/ven-sale.js: -------------------------------------------------------------------------------- 1 | const VEN = artifacts.require('./VEN.sol') 2 | const Sale = artifacts.require('./MockedVENSale.sol') 3 | const { assertFail, assertEqual } = require('./utils.js') 4 | var crypto = require('crypto') 5 | 6 | contract('Sale', accounts => { 7 | const ethValut = '0x' + crypto.randomBytes(20).toString('hex') 8 | const venVault = '0x' + crypto.randomBytes(20).toString('hex') 9 | 10 | const channel1Address = '0x' + crypto.randomBytes(20).toString('hex') 11 | 12 | const buyer1 = accounts[0] 13 | const buyer2 = accounts[1] 14 | const buyer3 = accounts[2] 15 | 16 | let ven 17 | let sale 18 | 19 | const totalSupply = web3.toWei(10 ** 9) 20 | const nonPubSupply = web3.toWei((10 ** 9) * 59 / 100) 21 | const pubSupply = web3.toWei((10 ** 9) * 41 / 100) 22 | 23 | const Stage = { 24 | Created: 1, 25 | Initialized: 2, 26 | Early: 3, 27 | Normal: 4, 28 | Closed: 5, 29 | Finalized: 6 30 | } 31 | 32 | it('deploy', async () => { 33 | // activate vault to decrease gas usage 34 | await web3.eth.sendTransaction({ to: ethValut, value: 0, from: accounts[0] }) 35 | ven = await VEN.new() 36 | sale = await Sale.new() 37 | // stage: created 38 | assertEqual(await sale.stage(), Stage.Created) 39 | 40 | // check constants 41 | assertEqual(await sale.totalSupply(), totalSupply) 42 | 43 | assertEqual(await sale.nonPublicSupply(), nonPubSupply) 44 | assertEqual(await sale.publicSupply(), pubSupply) 45 | }) 46 | 47 | const startTime = new Date("2017-08-18T12:00:00.000Z").getTime() / 1000 48 | const endTime = new Date("2017-08-31T12:00:00.000Z").getTime() / 1000 49 | const earlyStageLasts = 24 * 3600 * 3 50 | 51 | it('initialize', async () => { 52 | await ven.setOwner(sale.address) 53 | await sale.initialize( 54 | ven.address, 55 | ethValut, 56 | venVault) 57 | 58 | // stage: initialized 59 | assertEqual(await sale.stage(), Stage.Initialized) 60 | 61 | assertEqual(await sale.officialLimit(), web3.toWei(64371825)) 62 | assertEqual((await sale.officialLimit()).add(await sale.channelsLimit()), pubSupply) 63 | 64 | // nonpublic supply minted after initialized 65 | assertEqual(await ven.totalSupply(), nonPubSupply) 66 | // to nonPubVenVault 67 | assertEqual(await ven.balanceOf(venVault), nonPubSupply) 68 | 69 | // check exchange rate 70 | assertEqual(await sale.exchangeRate(), 0) 71 | 72 | /// check params 73 | assertEqual(await sale.startTime(), startTime) 74 | assertEqual(await sale.endTime(), endTime) 75 | assertEqual(await sale.earlyStageLasts(), earlyStageLasts) 76 | }) 77 | 78 | it('early stage', async () => { 79 | const exchangeRate = 4025 80 | 81 | await sale.setMockedBlockTime(startTime) 82 | // stage: early 83 | assertEqual(await sale.stage(), Stage.Early) 84 | await sale.setMockedBlockTime(startTime + earlyStageLasts - 1) 85 | assertEqual(await sale.stage(), Stage.Early) 86 | 87 | assertEqual(await sale.exchangeRate(), exchangeRate) 88 | 89 | const ethVaultBalance = await web3.eth.getBalance(ethValut) 90 | const b1VenBalance = await ven.balanceOf(buyer1) 91 | 92 | // send 31 eth 93 | await sale.sendTransaction({ from: buyer1, value: web3.toWei(31) }) 94 | 95 | // interval limit 96 | await assertFail(sale.sendTransaction({ from: buyer1, value: web3.toWei(1) })) 97 | 98 | // buyer should received ven based on 30 eth due to eth limit 99 | assertEqual(await ven.balanceOf(buyer1), b1VenBalance.add(web3.toWei(exchangeRate * 30))) 100 | 101 | // eth vault should received 30 eth 102 | assertEqual(await web3.eth.getBalance(ethValut), ethVaultBalance.add(web3.toWei(30))) 103 | 104 | // small value should fail 105 | await assertFail(sale.sendTransaction({ from: buyer1, value: web3.toWei(0.001) })) 106 | }) 107 | 108 | it('normal stage', async () => { 109 | const exchangeRate = 3500 110 | await sale.setMockedBlockTime(startTime + earlyStageLasts) 111 | // stage: normal 112 | assertEqual(await sale.stage(), Stage.Normal) 113 | await sale.setMockedBlockTime(endTime - 1) 114 | assertEqual(await sale.stage(), Stage.Normal) 115 | 116 | assertEqual(await sale.exchangeRate(), exchangeRate) 117 | 118 | const ethVaultBalance = web3.eth.getBalance(ethValut) 119 | const b2VenBalance = await ven.balanceOf(buyer2) 120 | // buy 121 | await sale.sendTransaction({ from: buyer2, value: web3.toWei(1) }) 122 | 123 | // buyer should received ven 124 | assertEqual(await ven.balanceOf(buyer2), web3.toWei(exchangeRate)) 125 | 126 | // eth vault should received another 1 eth 127 | assertEqual(await web3.eth.getBalance(ethValut), ethVaultBalance.add(web3.toWei(1))) 128 | }) 129 | 130 | it('offer to channels', async () => { 131 | const sold = await sale.channelsSold() 132 | const supply = await ven.totalSupply() 133 | 134 | const offer = 100 135 | 136 | await sale.offerToChannel(channel1Address, offer) 137 | 138 | assertEqual(await sale.channelsSold(), sold.add(offer)) 139 | assertEqual(await ven.totalSupply(), supply.add(offer)) 140 | assertEqual(await ven.balanceOf(channel1Address), offer) 141 | }) 142 | 143 | it('closed stage', async () => { 144 | await sale.setMockedBlockTime(endTime) 145 | // stage: closed 146 | assertEqual(await sale.stage(), Stage.Closed) 147 | // buy should fail 148 | await assertFail(sale.sendTransaction({ from: buyer1, value: web3.toWei(1) })) 149 | }) 150 | 151 | it('finalized stage', async () => { 152 | await sale.setMockedBlockTime(endTime - 1) 153 | // can't finalize before closed stage 154 | await assertFail(sale.finalize()) 155 | 156 | await sale.setMockedBlockTime(endTime) 157 | await sale.finalize() 158 | // stage: finalized 159 | assertEqual(await sale.stage(), Stage.Finalized) 160 | 161 | assertEqual(await ven.owner(), "0x0000000000000000000000000000000000000000") 162 | 163 | }) 164 | }) 165 | 166 | -------------------------------------------------------------------------------- /test/exchange.js: -------------------------------------------------------------------------------- 1 | const MockedExchange = artifacts.require('./MockedExchange.sol') 2 | const VEN = artifacts.require('./VEN.sol') 3 | 4 | 5 | 6 | const { assertFail, assertEqual } = require('./utils.js') 7 | var crypto = require('crypto') 8 | 9 | contract('MockedExchange', accounts => { 10 | const acc0 = accounts[0] 11 | const acc1 = accounts[1] 12 | const acc2 = accounts[2] 13 | const acc3 = accounts[3] 14 | 15 | const max_returned = 402500 16 | const ex_rate = 4025 //for test purpose, set to 402500 for easy test 17 | 18 | let exchange 19 | let ven 20 | 21 | it('deploy', async () => { 22 | exchange = await MockedExchange.new() 23 | ven = await VEN.new() 24 | 25 | console.log("exchange address=\t" + exchange.address) 26 | console.log("ven address=\t" + ven.address) 27 | 28 | // check init params 29 | 30 | //assertEqual(await rollback.tokenVault(), 0) 31 | // check init params 32 | assertEqual(await exchange.rate(), 4025) 33 | assertEqual(await exchange.tokenQuota(), 402500 * (10 ** 18)) 34 | assertEqual(await exchange.tokenToEtherAllowed(), true) 35 | assertEqual(await ven.balanceOf(exchange.address), 0) 36 | //for test purpose, ven init to 0 and then can be customized 37 | assertEqual(await exchange.token(), 0xD850942eF8811f2A866692A623011bDE52a462C1) 38 | 39 | 40 | //display all the balance of accounts 41 | console.log("account[0] balance is\t" + await web3.fromWei(web3.eth.getBalance(acc0))); 42 | console.log("account[1] balance is\t" + await web3.fromWei(web3.eth.getBalance(acc1))); 43 | console.log("account[2] balance is\t" + await web3.fromWei(web3.eth.getBalance(acc2))); 44 | console.log("account[3] balance is\t" + await web3.fromWei(web3.eth.getBalance(acc3))); 45 | }) 46 | 47 | //ven address 48 | it('setToken', async () => { 49 | await exchange.setToken(ven.address); 50 | assertEqual(await exchange.token(), ven.address) 51 | }) 52 | 53 | 54 | //exchange rate 55 | it('setRate', async () => { 56 | await exchange.setRate(1000); 57 | assertEqual(await exchange.rate(), 1000) 58 | await exchange.setRate(ex_rate); 59 | assertEqual(await exchange.rate(), ex_rate) 60 | }) 61 | 62 | //maximum token returned for each address 63 | it('setTokenQuota', async () => { 64 | await exchange.setTokenQuota(1000 * (10 ** 18)); 65 | assertEqual(await exchange.tokenQuota(), 1000 * (10 ** 18)) 66 | await exchange.setTokenQuota(max_returned); 67 | assertEqual(await exchange.tokenQuota(), max_returned) 68 | }) 69 | 70 | //tokentoether allowed flag 71 | it('setTokenToEtherAllowed', async () => { 72 | await exchange.setTokenToEtherAllowed(false); 73 | assertEqual(await exchange.tokenToEtherAllowed(), false) 74 | await exchange.setTokenToEtherAllowed(true); 75 | assertEqual(await exchange.tokenToEtherAllowed(), true) 76 | }) 77 | 78 | //withdraw ether 79 | it('withdrawEther', async () => { 80 | assertEqual(await web3.eth.getBalance(exchange.address), 0); 81 | await exchange.sendTransaction({ from: acc0, value: web3.toWei(5) }) 82 | await exchange.withdrawEther(acc0, web3.toWei(1)); 83 | assertEqual(await web3.eth.getBalance(exchange.address), web3.toWei(4)); 84 | }) 85 | 86 | //approve and call 87 | it('receiveApproval', async () => { 88 | assertEqual(await ven.balanceOf(acc0), 0) 89 | assertEqual(await ven.balanceOf(acc1), 0) 90 | assertEqual(await ven.balanceOf(acc2), 0) 91 | assertEqual(await ven.balanceOf(acc3), 0) 92 | 93 | //mint in ven contract 94 | const ven_credit_acc0 = max_returned; 95 | const ven_credit_acc1 = max_returned + 10; 96 | const ven_credit_acc2 = max_returned * 2; 97 | const ven_credit_acc3 = max_returned * 3; 98 | 99 | await ven.mint(acc0, ven_credit_acc0, true, 0x1) 100 | await ven.mint(acc1, ven_credit_acc1, true, 0x2) 101 | await ven.mint(acc2, ven_credit_acc2, true, 0x3) 102 | await ven.mint(acc3, ven_credit_acc3, true, 0x4) 103 | 104 | assertEqual(await ven.balanceOf(acc0), ven_credit_acc0) 105 | assertEqual(await ven.balanceOf(acc1), ven_credit_acc1) 106 | assertEqual(await ven.balanceOf(acc2), ven_credit_acc2) 107 | assertEqual(await ven.balanceOf(acc3), ven_credit_acc3) 108 | 109 | //approve and call in ven 110 | //withdraw from ven to exchange 111 | //to transfer from, it must sealed first; 112 | await ven.seal(); 113 | assertEqual(await ven.isSealed(), true); 114 | 115 | var bal_exchange = await web3.eth.getBalance(exchange.address) 116 | 117 | //test half exchange 118 | await ven.approveAndCall(exchange.address, ven_credit_acc0 / 2, '', { from: acc0 }); 119 | assertEqual(await ven.balanceOf(acc0), ven_credit_acc0 / 2) 120 | assertEqual(await ven.balanceOf(exchange.address), ven_credit_acc0 / 2) 121 | assertEqual(await web3.eth.getBalance(exchange.address), bal_exchange.sub(ven_credit_acc0 / 2 / ex_rate)); 122 | assertEqual(await exchange.quotaUsed(acc0), ven_credit_acc0 / 2) 123 | 124 | 125 | var bal_exchange = await web3.eth.getBalance(exchange.address) 126 | await ven.approveAndCall(exchange.address, ven_credit_acc0 / 2, '', { from: acc0 }); 127 | assertEqual(await ven.balanceOf(acc0), 0) 128 | assertEqual(await ven.balanceOf(exchange.address), ven_credit_acc0) 129 | assertEqual(await web3.eth.getBalance(exchange.address), bal_exchange.sub(ven_credit_acc0 / 2 / ex_rate)); 130 | assertEqual(await exchange.quotaUsed(acc0), ven_credit_acc0) 131 | 132 | 133 | 134 | var bal_exchange = await web3.eth.getBalance(exchange.address) 135 | 136 | //test max exchange+10 137 | await ven.approveAndCall(exchange.address, ven_credit_acc1, '', { from: acc1 }); 138 | assertEqual(await ven.balanceOf(acc1), 10) 139 | assertEqual(await ven.balanceOf(exchange.address), ven_credit_acc0 + ven_credit_acc1 - 10) 140 | assertEqual(await web3.eth.getBalance(exchange.address), bal_exchange.sub((ven_credit_acc1 - 10) / ex_rate)); 141 | await assertFail(ven.approveAndCall(exchange.address, 1, '', { from: acc1 })); //more that what can exchange 142 | assertEqual(await exchange.quotaUsed(acc1), ven_credit_acc1 - 10) 143 | }) 144 | 145 | 146 | 147 | 148 | it('withdrawToken', async () => { 149 | const tempAcct = '0x' + crypto.randomBytes(20).toString('hex') 150 | 151 | await exchange.withdrawToken(tempAcct, 1, { from: acc0 }) 152 | assertEqual(await ven.balanceOf(tempAcct), 1) 153 | }) 154 | }) -------------------------------------------------------------------------------- /test/rollback.js: -------------------------------------------------------------------------------- 1 | const MockedRollback = artifacts.require('./MockedRollback.sol') 2 | const VEN = artifacts.require('./VEN.sol') 3 | 4 | 5 | 6 | const { assertFail, assertEqual } = require('./utils.js') 7 | var crypto = require('crypto') 8 | 9 | contract('Rollback', accounts => { 10 | const acc1 = accounts[0] 11 | const acc2 = accounts[1] 12 | const acc3 = accounts[2] 13 | const acc4 = accounts[3] 14 | 15 | 16 | 17 | let rollback 18 | let ven 19 | 20 | it('deploy', async () => { 21 | rollback = await MockedRollback.new() 22 | ven = await VEN.new() 23 | 24 | console.log("rollback address=\t" + rollback.address) 25 | console.log("ven address=\t" + ven.address) 26 | 27 | // check init params 28 | assertEqual(await rollback.totalSetCredit(), 0) 29 | assertEqual(await rollback.totalReturnedCredit(), 0) 30 | //assertEqual(await rollback.tokenVault(), 0) 31 | 32 | //for test purpose, ven init to 0 and then can be customized 33 | assertEqual(await rollback.token(), 0xD850942eF8811f2A866692A623011bDE52a462C1) 34 | 35 | }) 36 | 37 | // it('setVENVault', async() => { 38 | 39 | // await rollback.setTokenVault(0x00112233445566778899AABBCCDDEEFF00112233) 40 | // assertEqual(await rollback.tokenVault(), 0x00112233445566778899AABBCCDDEEFF00112233) 41 | // }) 42 | 43 | 44 | it('setVen', async () => { 45 | //this is only for test, set ven address and run the co-operate test 46 | await rollback.setToken(ven.address); 47 | assertEqual(await rollback.token(), ven.address) 48 | }) 49 | 50 | it('setCredit', async () => { 51 | 52 | //credit shuold be set first 53 | const credit_acc0 = 100 * (10 ** 18); 54 | const credit_acc1 = 200 * (10 ** 18); 55 | const credit_acc2 = 300 * (10 ** 18); 56 | const credit_acc3 = 400 * (10 ** 18); 57 | 58 | await rollback.setCredit(acc1, credit_acc0) 59 | await rollback.setCredit(acc2, credit_acc1) 60 | await rollback.setCredit(acc3, credit_acc2) 61 | await rollback.setCredit(acc4, credit_acc3) 62 | 63 | assertEqual((await rollback.getCredit(acc1))[0], credit_acc0) 64 | assertEqual((await rollback.getCredit(acc2))[0], credit_acc1) 65 | assertEqual((await rollback.getCredit(acc3))[0], credit_acc2) 66 | assertEqual((await rollback.getCredit(acc4))[0], credit_acc3) 67 | assertEqual(await rollback.totalSetCredit(), credit_acc0 + credit_acc1 + credit_acc2 + credit_acc3) 68 | 69 | }) 70 | 71 | it('withDrawETH', async () => { 72 | //this is only for test, set ven address and run the co-operate test 73 | assertEqual(await web3.eth.getBalance(rollback.address), 0); 74 | await rollback.sendTransaction({ from: acc1, value: web3.toWei(4) }) 75 | await rollback.withdrawETH(acc1, web3.toWei(1)); 76 | assertEqual(await web3.eth.getBalance(rollback.address), web3.toWei(3)); 77 | }) 78 | 79 | it('receiveApproval', async () => { 80 | 81 | let b1 = await ven.balanceOf(acc1) 82 | let b2 = await ven.balanceOf(acc2) 83 | let b3 = await ven.balanceOf(acc3) 84 | let b4 = await ven.balanceOf(acc4) 85 | 86 | assertEqual(await b1, 0) 87 | assertEqual(await b2, 0) 88 | assertEqual(await b3, 0) 89 | assertEqual(await b4, 0) 90 | 91 | 92 | //mint in ven contract 93 | const ven_credit_acc0 = 8050; 94 | const ven_credit_acc1 = 80500; 95 | const ven_credit_acc2 = 100000000; 96 | const ven_credit_acc3 = 200000000; 97 | 98 | await ven.mint(acc1, ven_credit_acc0, true, 0x1) 99 | await ven.mint(acc2, ven_credit_acc1, true, 0x2) 100 | await ven.mint(acc3, ven_credit_acc2, true, 0x3) 101 | await ven.mint(acc4, ven_credit_acc3, true, 0x4) 102 | 103 | assertEqual(await ven.balanceOf(acc1), ven_credit_acc0) 104 | assertEqual(await ven.balanceOf(acc2), ven_credit_acc1) 105 | assertEqual(await ven.balanceOf(acc3), ven_credit_acc2) 106 | assertEqual(await ven.balanceOf(acc4), ven_credit_acc3) 107 | 108 | 109 | //approve and call in ven 110 | //withdraw from ven to rollback 111 | //to transfer from, it must sealed first; 112 | await ven.seal(); 113 | assertEqual(await ven.isSealed(), true); 114 | 115 | //await assertFail(ven.approveAndCall(rollback.address, ven_credit_acc0 + 1, '', { from: acc1 })); 116 | await assertFail(ven.approveAndCall(rollback.address, 4024, '', { from: acc1 })); 117 | 118 | //test the withdraw 119 | 120 | await rollback.setCredit(acc1, ven_credit_acc0) 121 | 122 | let balance_temp; 123 | //withdraw 4025 124 | balance_temp = await web3.eth.getBalance(rollback.address) 125 | await ven.approveAndCall(rollback.address, ven_credit_acc0 / 2, '', { from: acc1 }); 126 | 127 | assertEqual(await ven.balanceOf(rollback.address), ven_credit_acc0 / 2) 128 | 129 | const credit = await rollback.getCredit(acc1) 130 | assertEqual(credit[0], ven_credit_acc0) 131 | assertEqual(credit[1], ven_credit_acc0 / 2) 132 | 133 | assertEqual(await ven.balanceOf(acc1), ven_credit_acc0 / 2); 134 | assertEqual(await web3.eth.getBalance(rollback.address), balance_temp.sub(ven_credit_acc0 / 2 / 4025)); 135 | assertEqual(await rollback.totalReturnedCredit(), ven_credit_acc0 / 2); 136 | 137 | //withdraw the left 4025 138 | balance_temp = await web3.eth.getBalance(rollback.address) 139 | await ven.approveAndCall(rollback.address, ven_credit_acc0 / 2, '', { from: acc1 }); 140 | assertEqual(await ven.balanceOf(acc1), 0); 141 | assertEqual(await web3.eth.getBalance(rollback.address), balance_temp.sub(ven_credit_acc0 / 2 / 4025)); 142 | assertEqual(await rollback.totalReturnedCredit(), ven_credit_acc0); 143 | 144 | balance_temp = await web3.eth.getBalance(rollback.address) 145 | await ven.approveAndCall(rollback.address, ven_credit_acc1 / 2, '', { from: acc2 }); 146 | assertEqual(await ven.balanceOf(acc2), ven_credit_acc1 / 2); 147 | assertEqual(await web3.eth.getBalance(rollback.address), balance_temp.sub((ven_credit_acc1 / 2 / 4025))); 148 | assertEqual(await rollback.totalReturnedCredit(), ven_credit_acc0 + ven_credit_acc1 / 2); 149 | 150 | //withdraw the left 4025 151 | balance_temp = await web3.eth.getBalance(rollback.address) 152 | await ven.approveAndCall(rollback.address, ven_credit_acc1 / 2, '', { from: acc2 }); 153 | assertEqual(await ven.balanceOf(acc2), 0); 154 | assertEqual(await web3.eth.getBalance(rollback.address), balance_temp.sub((ven_credit_acc1 / 2 / 4025))); 155 | assertEqual(await rollback.totalReturnedCredit(), ven_credit_acc0 + ven_credit_acc1); 156 | 157 | }) 158 | 159 | it('withdrawToken', async () => { 160 | const tempAcct = '0x' + crypto.randomBytes(20).toString('hex') 161 | 162 | await rollback.withdrawToken(tempAcct, 1, { from: acc1 }) 163 | assertEqual(await ven.balanceOf(tempAcct), 1) 164 | }) 165 | 166 | }) -------------------------------------------------------------------------------- /contracts/Ven.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import "./Token.sol"; 4 | import "./Owned.sol"; 5 | import "./SafeMath.sol"; 6 | 7 | /// VEN token, ERC20 compliant 8 | contract VEN is Token, Owned { 9 | using SafeMath for uint256; 10 | 11 | string public constant name = "VeChain Token"; //The Token's name 12 | uint8 public constant decimals = 18; //Number of decimals of the smallest unit 13 | string public constant symbol = "VEN"; //An identifier 14 | 15 | // packed to 256bit to save gas usage. 16 | struct Supplies { 17 | // uint128's max value is about 3e38. 18 | // it's enough to present amount of tokens 19 | uint128 total; 20 | uint128 rawTokens; 21 | } 22 | 23 | Supplies supplies; 24 | 25 | // Packed to 256bit to save gas usage. 26 | struct Account { 27 | // uint112's max value is about 5e33. 28 | // it's enough to present amount of tokens 29 | uint112 balance; 30 | 31 | // raw token can be transformed into balance with bonus 32 | uint112 rawTokens; 33 | 34 | // safe to store timestamp 35 | uint32 lastMintedTimestamp; 36 | } 37 | 38 | // Balances for each account 39 | mapping(address => Account) accounts; 40 | 41 | // Owner of account approves the transfer of an amount to another account 42 | mapping(address => mapping(address => uint256)) allowed; 43 | 44 | // bonus that can be shared by raw tokens 45 | uint256 bonusOffered; 46 | 47 | // Constructor 48 | function VEN() { 49 | } 50 | 51 | function totalSupply() constant returns (uint256 supply){ 52 | return supplies.total; 53 | } 54 | 55 | // Send back ether sent to me 56 | function () { 57 | revert(); 58 | } 59 | 60 | // If sealed, transfer is enabled and mint is disabled 61 | function isSealed() constant returns (bool) { 62 | return owner == 0; 63 | } 64 | 65 | function lastMintedTimestamp(address _owner) constant returns(uint32) { 66 | return accounts[_owner].lastMintedTimestamp; 67 | } 68 | 69 | // Claim bonus by raw tokens 70 | function claimBonus(address _owner) internal{ 71 | require(isSealed()); 72 | if (accounts[_owner].rawTokens != 0) { 73 | uint256 realBalance = balanceOf(_owner); 74 | uint256 bonus = realBalance 75 | .sub(accounts[_owner].balance) 76 | .sub(accounts[_owner].rawTokens); 77 | 78 | accounts[_owner].balance = realBalance.toUINT112(); 79 | accounts[_owner].rawTokens = 0; 80 | if(bonus > 0){ 81 | Transfer(this, _owner, bonus); 82 | } 83 | } 84 | } 85 | 86 | // What is the balance of a particular account? 87 | function balanceOf(address _owner) constant returns (uint256 balance) { 88 | if (accounts[_owner].rawTokens == 0) 89 | return accounts[_owner].balance; 90 | 91 | if (bonusOffered > 0) { 92 | uint256 bonus = bonusOffered 93 | .mul(accounts[_owner].rawTokens) 94 | .div(supplies.rawTokens); 95 | 96 | return bonus.add(accounts[_owner].balance) 97 | .add(accounts[_owner].rawTokens); 98 | } 99 | 100 | return uint256(accounts[_owner].balance) 101 | .add(accounts[_owner].rawTokens); 102 | } 103 | 104 | // Transfer the balance from owner's account to another account 105 | function transfer(address _to, uint256 _amount) returns (bool success) { 106 | require(isSealed()); 107 | 108 | // implicitly claim bonus for both sender and receiver 109 | claimBonus(msg.sender); 110 | claimBonus(_to); 111 | 112 | // according to VEN's total supply, never overflow here 113 | if (accounts[msg.sender].balance >= _amount 114 | && _amount > 0) { 115 | accounts[msg.sender].balance -= uint112(_amount); 116 | accounts[_to].balance = _amount.add(accounts[_to].balance).toUINT112(); 117 | Transfer(msg.sender, _to, _amount); 118 | return true; 119 | } else { 120 | return false; 121 | } 122 | } 123 | 124 | // Send _value amount of tokens from address _from to address _to 125 | // The transferFrom method is used for a withdraw workflow, allowing contracts to send 126 | // tokens on your behalf, for example to "deposit" to a contract address and/or to charge 127 | // fees in sub-currencies; the command should fail unless the _from account has 128 | // deliberately authorized the sender of the message via some mechanism; we propose 129 | // these standardized APIs for approval: 130 | function transferFrom( 131 | address _from, 132 | address _to, 133 | uint256 _amount 134 | ) returns (bool success) { 135 | require(isSealed()); 136 | 137 | // implicitly claim bonus for both sender and receiver 138 | claimBonus(_from); 139 | claimBonus(_to); 140 | 141 | // according to VEN's total supply, never overflow here 142 | if (accounts[_from].balance >= _amount 143 | && allowed[_from][msg.sender] >= _amount 144 | && _amount > 0) { 145 | accounts[_from].balance -= uint112(_amount); 146 | allowed[_from][msg.sender] -= _amount; 147 | accounts[_to].balance = _amount.add(accounts[_to].balance).toUINT112(); 148 | Transfer(_from, _to, _amount); 149 | return true; 150 | } else { 151 | return false; 152 | } 153 | } 154 | 155 | // Allow _spender to withdraw from your account, multiple times, up to the _value amount. 156 | // If this function is called again it overwrites the current allowance with _value. 157 | function approve(address _spender, uint256 _amount) returns (bool success) { 158 | allowed[msg.sender][_spender] = _amount; 159 | Approval(msg.sender, _spender, _amount); 160 | return true; 161 | } 162 | 163 | /* Approves and then calls the receiving contract */ 164 | function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { 165 | allowed[msg.sender][_spender] = _value; 166 | Approval(msg.sender, _spender, _value); 167 | 168 | //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. 169 | //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) 170 | //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. 171 | //if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { revert(); } 172 | ApprovalReceiver(_spender).receiveApproval(msg.sender, _value, this, _extraData); 173 | return true; 174 | } 175 | 176 | function allowance(address _owner, address _spender) constant returns (uint256 remaining) { 177 | return allowed[_owner][_spender]; 178 | } 179 | 180 | // Mint tokens and assign to some one 181 | function mint(address _owner, uint256 _amount, bool _isRaw, uint32 timestamp) onlyOwner{ 182 | if (_isRaw) { 183 | accounts[_owner].rawTokens = _amount.add(accounts[_owner].rawTokens).toUINT112(); 184 | supplies.rawTokens = _amount.add(supplies.rawTokens).toUINT128(); 185 | } else { 186 | accounts[_owner].balance = _amount.add(accounts[_owner].balance).toUINT112(); 187 | } 188 | 189 | accounts[_owner].lastMintedTimestamp = timestamp; 190 | 191 | supplies.total = _amount.add(supplies.total).toUINT128(); 192 | Transfer(0, _owner, _amount); 193 | } 194 | 195 | // Offer bonus to raw tokens holder 196 | function offerBonus(uint256 _bonus) onlyOwner { 197 | bonusOffered = bonusOffered.add(_bonus); 198 | supplies.total = _bonus.add(supplies.total).toUINT128(); 199 | Transfer(0, this, _bonus); 200 | } 201 | 202 | // Set owner to zero address, to disable mint, and enable token transfer 203 | function seal() onlyOwner { 204 | setOwner(0); 205 | } 206 | } 207 | 208 | contract ApprovalReceiver { 209 | function receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData); 210 | } 211 | -------------------------------------------------------------------------------- /contracts/VenSale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import "./Owned.sol"; 4 | import "./Ven.sol"; 5 | import "./SafeMath.sol"; 6 | 7 | // Contract to sell and distribute VEN tokens 8 | contract VENSale is Owned{ 9 | 10 | /// chart of stage transition 11 | /// 12 | /// deploy initialize startTime endTime finalize 13 | /// | <-earlyStageLasts-> | | <- closedStageLasts -> | 14 | /// O-----------O---------------O---------------------O-------------O------------------------O------------> 15 | /// Created Initialized Early Normal Closed Finalized 16 | enum Stage { 17 | NotCreated, 18 | Created, 19 | Initialized, 20 | Early, 21 | Normal, 22 | Closed, 23 | Finalized 24 | } 25 | 26 | using SafeMath for uint256; 27 | 28 | uint256 public constant totalSupply = (10 ** 9) * (10 ** 18); // 1 billion VEN, decimals set to 18 29 | 30 | uint256 constant privateSupply = totalSupply * 9 / 100; // 9% for private ICO 31 | uint256 constant commercialPlan = totalSupply * 23 / 100; // 23% for commercial plan 32 | uint256 constant reservedForTeam = totalSupply * 5 / 100; // 5% for team 33 | uint256 constant reservedForOperations = totalSupply * 22 / 100; // 22 for operations 34 | 35 | // 59% 36 | uint256 public constant nonPublicSupply = privateSupply + commercialPlan + reservedForTeam + reservedForOperations; 37 | // 41% 38 | uint256 public constant publicSupply = totalSupply - nonPublicSupply; 39 | 40 | 41 | uint256 public constant officialLimit = 64371825 * (10 ** 18); 42 | uint256 public constant channelsLimit = publicSupply - officialLimit; 43 | 44 | // packed to 256bit 45 | struct SoldOut { 46 | uint16 placeholder; // placeholder to make struct pre-alloced 47 | 48 | // amount of tokens officially sold out. 49 | // max value of 120bit is about 1e36, it's enough for token amount 50 | uint120 official; 51 | 52 | uint120 channels; // amount of tokens sold out via channels 53 | } 54 | 55 | SoldOut soldOut; 56 | 57 | uint256 constant venPerEth = 3500; // normal exchange rate 58 | uint256 constant venPerEthEarlyStage = venPerEth + venPerEth * 15 / 100; // early stage has 15% reward 59 | 60 | uint constant minBuyInterval = 30 minutes; // each account can buy once in 30 minutes 61 | uint constant maxBuyEthAmount = 30 ether; 62 | 63 | VEN ven; // VEN token contract follows ERC20 standard 64 | 65 | address ethVault; // the account to keep received ether 66 | address venVault; // the account to keep non-public offered VEN tokens 67 | 68 | uint public constant startTime = 1503057600; // time to start sale 69 | uint public constant endTime = 1504180800; // tiem to close sale 70 | uint public constant earlyStageLasts = 3 days; // early bird stage lasts in seconds 71 | 72 | bool initialized; 73 | bool finalized; 74 | 75 | function VENSale() { 76 | soldOut.placeholder = 1; 77 | } 78 | 79 | /// @notice calculte exchange rate according to current stage 80 | /// @return exchange rate. zero if not in sale. 81 | function exchangeRate() constant returns (uint256){ 82 | if (stage() == Stage.Early) { 83 | return venPerEthEarlyStage; 84 | } 85 | if (stage() == Stage.Normal) { 86 | return venPerEth; 87 | } 88 | return 0; 89 | } 90 | 91 | /// @notice for test purpose 92 | function blockTime() constant returns (uint32) { 93 | return uint32(block.timestamp); 94 | } 95 | 96 | /// @notice estimate stage 97 | /// @return current stage 98 | function stage() constant returns (Stage) { 99 | if (finalized) { 100 | return Stage.Finalized; 101 | } 102 | 103 | if (!initialized) { 104 | // deployed but not initialized 105 | return Stage.Created; 106 | } 107 | 108 | if (blockTime() < startTime) { 109 | // not started yet 110 | return Stage.Initialized; 111 | } 112 | 113 | if (uint256(soldOut.official).add(soldOut.channels) >= publicSupply) { 114 | // all sold out 115 | return Stage.Closed; 116 | } 117 | 118 | if (blockTime() < endTime) { 119 | // in sale 120 | if (blockTime() < startTime.add(earlyStageLasts)) { 121 | // early bird stage 122 | return Stage.Early; 123 | } 124 | // normal stage 125 | return Stage.Normal; 126 | } 127 | 128 | // closed 129 | return Stage.Closed; 130 | } 131 | 132 | function isContract(address _addr) constant internal returns(bool) { 133 | uint size; 134 | if (_addr == 0) return false; 135 | assembly { 136 | size := extcodesize(_addr) 137 | } 138 | return size > 0; 139 | } 140 | 141 | /// @notice entry to buy tokens 142 | function () payable { 143 | buy(); 144 | } 145 | 146 | /// @notice entry to buy tokens 147 | function buy() payable { 148 | // reject contract buyer to avoid breaking interval limit 149 | require(!isContract(msg.sender)); 150 | require(msg.value >= 0.01 ether); 151 | 152 | uint256 rate = exchangeRate(); 153 | // here don't need to check stage. rate is only valid when in sale 154 | require(rate > 0); 155 | // each account is allowed once in minBuyInterval 156 | require(blockTime() >= ven.lastMintedTimestamp(msg.sender) + minBuyInterval); 157 | 158 | uint256 requested; 159 | // and limited to maxBuyEthAmount 160 | if (msg.value > maxBuyEthAmount) { 161 | requested = maxBuyEthAmount.mul(rate); 162 | } else { 163 | requested = msg.value.mul(rate); 164 | } 165 | 166 | uint256 remained = officialLimit.sub(soldOut.official); 167 | if (requested > remained) { 168 | //exceed remained 169 | requested = remained; 170 | } 171 | 172 | uint256 ethCost = requested.div(rate); 173 | if (requested > 0) { 174 | ven.mint(msg.sender, requested, true, blockTime()); 175 | // transfer ETH to vault 176 | ethVault.transfer(ethCost); 177 | 178 | soldOut.official = requested.add(soldOut.official).toUINT120(); 179 | onSold(msg.sender, requested, ethCost); 180 | } 181 | 182 | uint256 toReturn = msg.value.sub(ethCost); 183 | if(toReturn > 0) { 184 | // return over payed ETH 185 | msg.sender.transfer(toReturn); 186 | } 187 | } 188 | 189 | /// @notice returns tokens sold officially 190 | function officialSold() constant returns (uint256) { 191 | return soldOut.official; 192 | } 193 | 194 | /// @notice returns tokens sold via channels 195 | function channelsSold() constant returns (uint256) { 196 | return soldOut.channels; 197 | } 198 | 199 | /// @notice manually offer tokens to channel 200 | function offerToChannel(address _channelAccount, uint256 _venAmount) onlyOwner { 201 | Stage stg = stage(); 202 | // since the settlement may be delayed, so it's allowed in closed stage 203 | require(stg == Stage.Early || stg == Stage.Normal || stg == Stage.Closed); 204 | 205 | soldOut.channels = _venAmount.add(soldOut.channels).toUINT120(); 206 | 207 | //should not exceed limit 208 | require(soldOut.channels <= channelsLimit); 209 | 210 | ven.mint( 211 | _channelAccount, 212 | _venAmount, 213 | true, // unsold tokens can be claimed by channels portion 214 | blockTime() 215 | ); 216 | 217 | onSold(_channelAccount, _venAmount, 0); 218 | } 219 | 220 | /// @notice initialize to prepare for sale 221 | /// @param _ven The address VEN token contract following ERC20 standard 222 | /// @param _ethVault The place to store received ETH 223 | /// @param _venVault The place to store non-publicly supplied VEN tokens 224 | function initialize( 225 | VEN _ven, 226 | address _ethVault, 227 | address _venVault) onlyOwner { 228 | require(stage() == Stage.Created); 229 | 230 | // ownership of token contract should already be this 231 | require(_ven.owner() == address(this)); 232 | 233 | require(address(_ethVault) != 0); 234 | require(address(_venVault) != 0); 235 | 236 | ven = _ven; 237 | 238 | ethVault = _ethVault; 239 | venVault = _venVault; 240 | 241 | ven.mint( 242 | venVault, 243 | reservedForTeam.add(reservedForOperations), 244 | false, // team and operations reserved portion can't share unsold tokens 245 | blockTime() 246 | ); 247 | 248 | ven.mint( 249 | venVault, 250 | privateSupply.add(commercialPlan), 251 | true, // private ICO and commercial plan can share unsold tokens 252 | blockTime() 253 | ); 254 | 255 | initialized = true; 256 | onInitialized(); 257 | } 258 | 259 | /// @notice finalize 260 | function finalize() onlyOwner { 261 | // only after closed stage 262 | require(stage() == Stage.Closed); 263 | 264 | uint256 unsold = publicSupply.sub(soldOut.official).sub(soldOut.channels); 265 | 266 | if (unsold > 0) { 267 | // unsold VEN as bonus 268 | ven.offerBonus(unsold); 269 | } 270 | ven.seal(); 271 | 272 | finalized = true; 273 | onFinalized(); 274 | } 275 | 276 | event onInitialized(); 277 | event onFinalized(); 278 | 279 | event onSold(address indexed buyer, uint256 venAmount, uint256 ethCost); 280 | } 281 | --------------------------------------------------------------------------------