├── .gitignore ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── truffle.js ├── README.md ├── startTestRPC.sh ├── contracts ├── Migrations.sol ├── SafeMath.sol ├── StandardToken.sol └── NEToken.sol ├── package.json ├── dist ├── ABI.json └── NEToken.sol └── test ├── EthCap.js ├── TokenCap.js ├── Refunding.js ├── EthRetrieval.js └── NEToken.js /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | package-lock.json 4 | *.iml 5 | .idea 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 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*", // Match any network id 7 | gas: 2099999 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nimiq-exchange-token 2 | Contribution Contract 3 | 4 | ## Run Testsuite 5 | - run `npm install` first 6 | - start TestRPC via `npm run testRPC` 7 | - then run the tests in a separate console window via `npm test` 8 | 9 | In order to rerun the tests, testRPC has to be restarted. 10 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var NEToken = artifacts.require("./NEToken.sol"); 2 | 3 | module.exports = function(deployer) { 4 | var ethfund = "0x39aeeba1b26c59bb0239a7740a3aaf7aa3bc94aa"; 5 | var start = 10; 6 | var middle = 15; 7 | var end = 20; 8 | deployer.deploy(NEToken, ethfund, start, end, middle); 9 | }; 10 | -------------------------------------------------------------------------------- /startTestRPC.sh: -------------------------------------------------------------------------------- 1 | node_modules/.bin/testrpc \ 2 | --account="0x0861f40ce48bc775a041d429b4afd2043b245f81be595728aa44831e417c8cfa,1000000000000000000000000" \ 3 | --account="0x5aece8ace015005e0d156348c62e66d99359f0957157e86f1418889a8225fdb3,5000000000000000000000" \ 4 | --account="0x3bede35d5b63255549db2150571a168e0464638201f2d4ae26e5a1dd84cf0780,50000000000000000000000" \ 5 | --account="0x2744bce920bd49e3864acd50195d49e5c55620ca362d3d48217c3901d181b43e,5000000000000000000000" \ 6 | --account="0x758a0979242d89f2b829b9cc44f77b34384e40d4edbe5f00e94cba71e01970c0,1000000000000000000000" 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | /* taking ideas from FirstBlood token */ 4 | contract SafeMath { 5 | 6 | function safeAdd(uint256 x, uint256 y) internal returns(uint256) { 7 | uint256 z = x + y; 8 | assert((z >= x) && (z >= y)); 9 | return z; 10 | } 11 | 12 | function safeSubtract(uint256 x, uint256 y) internal returns(uint256) { 13 | assert(x >= y); 14 | uint256 z = x - y; 15 | return z; 16 | } 17 | 18 | function safeMult(uint256 x, uint256 y) internal returns(uint256) { 19 | uint256 z = x * y; 20 | assert((x == 0)||(z/x == y)); 21 | return z; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nimiq-exchange-token", 3 | "version": "1.0.0", 4 | "homepage": "https://nimiq.com/", 5 | "description": "", 6 | "main": "contracts/NEToken.sol", 7 | "private": true, 8 | "scripts": { 9 | "testRPC": "./startTestRPC.sh", 10 | "build": "truffle compile", 11 | "test": "truffle test" 12 | }, 13 | "author": { 14 | "name": "The Nimiq Core Development Team", 15 | "url": "https://nimiq.com/" 16 | }, 17 | "license": "Apache-2.0", 18 | "bugs": { 19 | "url": "https://github.com/nimiq-network/nimiq-exchange-token/issues" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/nimiq-network/nimiq-exchange-token.git" 24 | }, 25 | "engines": { 26 | "node": ">=7.9.0" 27 | }, 28 | "devDependencies": { 29 | "ethereumjs-testrpc": "^3.0.5", 30 | "truffle": "^3.2.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/StandardToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import "./SafeMath.sol"; 4 | 5 | contract Token { 6 | uint256 public totalSupply; 7 | 8 | function balanceOf(address _owner) constant returns (uint256 balance); 9 | function transfer(address _to, uint256 _value) returns (bool success); 10 | function transferFrom(address _from, address _to, uint256 _value) returns (bool success); 11 | function approve(address _spender, uint256 _value) returns (bool success); 12 | function allowance(address _owner, address _spender) constant returns (uint256 remaining); 13 | 14 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 15 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 16 | } 17 | 18 | /* ERC 20 token */ 19 | contract StandardToken is Token, SafeMath { 20 | 21 | mapping (address => uint256) balances; 22 | mapping (address => mapping (address => uint256)) allowed; 23 | 24 | modifier onlyPayloadSize(uint numwords) { 25 | assert(msg.data.length == numwords * 32 + 4); 26 | _; 27 | } 28 | 29 | function transfer(address _to, uint256 _value) 30 | returns (bool success) 31 | { 32 | if (balances[msg.sender] >= _value && _value > 0 && balances[_to] + _value > balances[_to]) { 33 | balances[msg.sender] = safeSubtract(balances[msg.sender], _value); 34 | balances[_to] = safeAdd(balances[_to], _value); 35 | Transfer(msg.sender, _to, _value); 36 | return true; 37 | } else { 38 | return false; 39 | } 40 | } 41 | 42 | function transferFrom(address _from, address _to, uint256 _value) 43 | returns (bool success) 44 | { 45 | if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0 && balances[_to] + _value > balances[_to]) { 46 | balances[_to] = safeAdd(balances[_to], _value); 47 | balances[_from] = safeSubtract(balances[_from], _value); 48 | allowed[_from][msg.sender] = safeSubtract(allowed[_from][msg.sender], _value); 49 | Transfer(_from, _to, _value); 50 | return true; 51 | } else { 52 | return false; 53 | } 54 | } 55 | 56 | function balanceOf(address _owner) constant returns (uint256 balance) { 57 | return balances[_owner]; 58 | } 59 | 60 | function approve(address _spender, uint256 _value) 61 | onlyPayloadSize(2) 62 | returns (bool success) 63 | { 64 | allowed[msg.sender][_spender] = _value; 65 | Approval(msg.sender, _spender, _value); 66 | return true; 67 | } 68 | 69 | function allowance(address _owner, address _spender) 70 | constant 71 | onlyPayloadSize(2) 72 | returns (uint256 remaining) 73 | { 74 | return allowed[_owner][_spender]; 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /dist/ABI.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"TOKEN_SECOND_EXCHANGE_RATE","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"TOKEN_FIRST_EXCHANGE_RATE","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"ETH_RECEIVED_MIN","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"nimiqAddress","type":"bytes32"}],"name":"redeemTokens","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"exchangeRateChangesBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"proceed","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finalize","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"refund","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalReceivedEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fundingEndBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"ethFundDeposit","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"createTokens","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"state","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fundingStartBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"ETH_RECEIVED_CAP","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"startRedeeming","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"retrieveEth","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"TOKEN_MIN","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"TOKEN_CREATION_CAP","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"_ethFundDeposit","type":"address"},{"name":"_fundingStartBlock","type":"uint256"},{"name":"_fundingEndBlock","type":"uint256"},{"name":"_exchangeRateChangesBlock","type":"uint256"}],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"LogRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"LogCreateNET","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"},{"indexed":false,"name":"_nimiqAddress","type":"bytes32"}],"name":"LogRedeemNET","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}] -------------------------------------------------------------------------------- /contracts/NEToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import "./StandardToken.sol"; 4 | 5 | /* Taking ideas from BAT token */ 6 | contract NEToken is StandardToken { 7 | 8 | // Token metadata 9 | string public constant name = "Nimiq Network Interim Token"; 10 | string public constant symbol = "NET"; 11 | uint256 public constant decimals = 18; 12 | string public version = "0.8"; 13 | 14 | // Deposit address of Multisig account controlled by the creators 15 | address public ethFundDeposit; 16 | 17 | // Fundraising parameters 18 | enum ContractState { Fundraising, Finalized, Redeeming, Paused } 19 | ContractState public state; // Current state of the contract 20 | ContractState private savedState; // State of the contract before pause 21 | 22 | uint256 public fundingStartBlock; // These two blocks need to be chosen to comply with the 23 | uint256 public fundingEndBlock; // start date and 28 day duration requirements 24 | uint256 public exchangeRateChangesBlock; // block number that triggers the exchange rate change 25 | 26 | uint256 public constant TOKEN_FIRST_EXCHANGE_RATE = 175; // 175 NETs per 1 ETH 27 | uint256 public constant TOKEN_SECOND_EXCHANGE_RATE = 125; // 125 NETs per 1 ETH 28 | uint256 public constant TOKEN_CREATION_CAP = 10.5 * (10**6) * 10**decimals; // 10.5 million NETs 29 | uint256 public constant ETH_RECEIVED_CAP = 60 * (10**3) * 10**decimals; // 60 000 ETH 30 | uint256 public constant ETH_RECEIVED_MIN = 5 * (10**3) * 10**decimals; // 5 000 ETH 31 | uint256 public constant TOKEN_MIN = 1 * 10**decimals; // 1 NET 32 | 33 | // We need to keep track of how much ether have been contributed, since we have a cap for ETH too 34 | uint256 public totalReceivedEth = 0; 35 | 36 | // Since we have different exchange rates at different stages, we need to keep track 37 | // of how much ether each contributed in case that we need to issue a refund 38 | mapping (address => uint256) private ethBalances; 39 | 40 | // Events used for logging 41 | event LogRefund(address indexed _to, uint256 _value); 42 | event LogCreateNET(address indexed _to, uint256 _value); 43 | event LogRedeemNET(address indexed _to, uint256 _value, bytes32 _nimiqAddress); 44 | 45 | modifier isFinalized() { 46 | require(state == ContractState.Finalized); 47 | _; 48 | } 49 | 50 | modifier isFundraising() { 51 | require(state == ContractState.Fundraising); 52 | _; 53 | } 54 | 55 | modifier isRedeeming() { 56 | require(state == ContractState.Redeeming); 57 | _; 58 | } 59 | 60 | modifier isPaused() { 61 | require(state == ContractState.Paused); 62 | _; 63 | } 64 | 65 | modifier notPaused() { 66 | require(state != ContractState.Paused); 67 | _; 68 | } 69 | 70 | modifier isFundraisingIgnorePaused() { 71 | require(state == ContractState.Fundraising || (state == ContractState.Paused && savedState == ContractState.Fundraising)); 72 | _; 73 | } 74 | 75 | modifier onlyOwner() { 76 | require(msg.sender == ethFundDeposit); 77 | _; 78 | } 79 | 80 | modifier minimumReached() { 81 | require(totalReceivedEth >= ETH_RECEIVED_MIN); 82 | _; 83 | } 84 | 85 | // Constructor 86 | function NEToken( 87 | address _ethFundDeposit, 88 | uint256 _fundingStartBlock, 89 | uint256 _fundingEndBlock, 90 | uint256 _exchangeRateChangesBlock) 91 | { 92 | // Check that the parameters make sense 93 | require(block.number <= _fundingStartBlock); // The start of the fundraising should happen in the future 94 | require(_fundingStartBlock <= _exchangeRateChangesBlock); // The exchange rate change should happen after the start of the fundraising 95 | require(_exchangeRateChangesBlock <= _fundingEndBlock); // And the end of the fundraising should happen after the exchange rate change 96 | 97 | // Contract state 98 | state = ContractState.Fundraising; 99 | savedState = ContractState.Fundraising; 100 | 101 | ethFundDeposit = _ethFundDeposit; 102 | fundingStartBlock = _fundingStartBlock; 103 | fundingEndBlock = _fundingEndBlock; 104 | exchangeRateChangesBlock = _exchangeRateChangesBlock; 105 | totalSupply = 0; 106 | } 107 | 108 | // Overridden method to check for end of fundraising before allowing transfer of tokens 109 | function transfer(address _to, uint256 _value) 110 | isFinalized // Only allow token transfer after the fundraising has ended 111 | onlyPayloadSize(2) 112 | returns (bool success) 113 | { 114 | return super.transfer(_to, _value); 115 | } 116 | 117 | 118 | // Overridden method to check for end of fundraising before allowing transfer of tokens 119 | function transferFrom(address _from, address _to, uint256 _value) 120 | isFinalized // Only allow token transfer after the fundraising has ended 121 | onlyPayloadSize(3) 122 | returns (bool success) 123 | { 124 | return super.transferFrom(_from, _to, _value); 125 | } 126 | 127 | 128 | /// @dev Accepts ether and creates new NET tokens 129 | function createTokens() 130 | payable 131 | external 132 | isFundraising 133 | { 134 | require(block.number >= fundingStartBlock); 135 | require(block.number <= fundingEndBlock); 136 | require(msg.value > 0); 137 | 138 | // First we check the ETH cap, as it's easier to calculate, return 139 | // the contribution if the cap has been reached already 140 | uint256 checkedReceivedEth = safeAdd(totalReceivedEth, msg.value); 141 | require(checkedReceivedEth <= ETH_RECEIVED_CAP); 142 | 143 | // If all is fine with the ETH cap, we continue to check the 144 | // minimum amount of tokens and the cap for how many tokens 145 | // have been generated so far 146 | uint256 tokens = safeMult(msg.value, getCurrentTokenPrice()); 147 | require(tokens >= TOKEN_MIN); 148 | uint256 checkedSupply = safeAdd(totalSupply, tokens); 149 | require(checkedSupply <= TOKEN_CREATION_CAP); 150 | 151 | // Only when all the checks have passed, then we update the state (ethBalances, 152 | // totalReceivedEth, totalSupply, and balances) of the contract 153 | ethBalances[msg.sender] = safeAdd(ethBalances[msg.sender], msg.value); 154 | totalReceivedEth = checkedReceivedEth; 155 | totalSupply = checkedSupply; 156 | balances[msg.sender] += tokens; // safeAdd not needed; bad semantics to use here 157 | 158 | // Log the creation of this tokens 159 | LogCreateNET(msg.sender, tokens); 160 | } 161 | 162 | 163 | /// @dev Returns the current token price 164 | function getCurrentTokenPrice() 165 | private 166 | constant 167 | returns (uint256 currentPrice) 168 | { 169 | if (block.number < exchangeRateChangesBlock) { 170 | return TOKEN_FIRST_EXCHANGE_RATE; 171 | } else { 172 | return TOKEN_SECOND_EXCHANGE_RATE; 173 | } 174 | } 175 | 176 | 177 | /// @dev Redeems NETs and records the Nimiq address of the sender 178 | function redeemTokens(bytes32 nimiqAddress) 179 | external 180 | isRedeeming 181 | { 182 | uint256 netVal = balances[msg.sender]; 183 | require(netVal >= TOKEN_MIN); // At least TOKEN_MIN tokens have to be redeemed 184 | 185 | // Move the tokens of the caller to Nimiq's address 186 | if (!super.transfer(ethFundDeposit, netVal)) throw; 187 | 188 | // Log the redeeming of this tokens 189 | LogRedeemNET(msg.sender, netVal, nimiqAddress); 190 | } 191 | 192 | 193 | /// @dev Allows to transfer ether from the contract as soon as the minimum is reached 194 | function retrieveEth(uint256 _value) 195 | external 196 | minimumReached 197 | onlyOwner 198 | { 199 | require(_value <= this.balance); 200 | 201 | // send the eth to Nimiq Creators 202 | ethFundDeposit.transfer(_value); 203 | } 204 | 205 | 206 | /// @dev Ends the fundraising period and sends the ETH to the Multisig wallet 207 | function finalize() 208 | external 209 | isFundraising 210 | minimumReached 211 | onlyOwner // Only the owner of the ethFundDeposit address can finalize the contract 212 | { 213 | require(block.number > fundingEndBlock || totalSupply >= TOKEN_CREATION_CAP || totalReceivedEth >= ETH_RECEIVED_CAP); // Only allow to finalize the contract before the ending block if we already reached any of the two caps 214 | 215 | // Move the contract to Finalized state 216 | state = ContractState.Finalized; 217 | savedState = ContractState.Finalized; 218 | 219 | // Send the ETH to Nimiq Creators 220 | ethFundDeposit.transfer(this.balance); 221 | } 222 | 223 | 224 | /// @dev Starts the redeeming period 225 | function startRedeeming() 226 | external 227 | isFinalized // The redeeming period can only be started after the contract is finalized 228 | onlyOwner // Only the owner of the ethFundDeposit address can start the redeeming period 229 | { 230 | // Move the contract to Redeeming state 231 | state = ContractState.Redeeming; 232 | savedState = ContractState.Redeeming; 233 | } 234 | 235 | 236 | /// @dev Pauses the contract 237 | function pause() 238 | external 239 | notPaused // Prevent the contract getting stuck in the Paused state 240 | onlyOwner // Only the owner of the ethFundDeposit address can pause the contract 241 | { 242 | // Move the contract to Paused state 243 | savedState = state; 244 | state = ContractState.Paused; 245 | } 246 | 247 | 248 | /// @dev Proceeds with the contract 249 | function proceed() 250 | external 251 | isPaused 252 | onlyOwner // Only the owner of the ethFundDeposit address can proceed with the contract 253 | { 254 | // Move the contract to the previous state 255 | state = savedState; 256 | } 257 | 258 | 259 | /// @dev Allows contributors to recover their ether in case the minimum funding goal is not reached 260 | function refund() 261 | external 262 | isFundraisingIgnorePaused // Refunding is only possible in the fundraising phase (no matter if paused) by definition 263 | { 264 | require(block.number > fundingEndBlock); // Prevents refund until fundraising period is over 265 | require(totalReceivedEth < ETH_RECEIVED_MIN); // No refunds if the minimum has been reached 266 | 267 | uint256 netVal = balances[msg.sender]; 268 | require(netVal > 0); 269 | uint256 ethVal = ethBalances[msg.sender]; 270 | require(ethVal > 0); 271 | 272 | // Update the state only after all the checks have passed 273 | balances[msg.sender] = 0; 274 | ethBalances[msg.sender] = 0; 275 | totalSupply = safeSubtract(totalSupply, netVal); // Extra safe 276 | 277 | // Log this refund 278 | LogRefund(msg.sender, ethVal); 279 | 280 | // Send the contributions only after we have updated all the balances 281 | // If you're using a contract, make sure it works with .transfer() gas limits 282 | msg.sender.transfer(ethVal); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /test/EthCap.js: -------------------------------------------------------------------------------- 1 | // Specifically request an abstraction for NEToken 2 | var NEToken = artifacts.require("NEToken"); 3 | 4 | async function mineBlocks(num=1) { 5 | for (let i=0; i { 62 | net = instance; 63 | return net.fundingStartBlock.call(); 64 | }).then(result => { 65 | fundingStartBlock = convertInt(result); 66 | return net.exchangeRateChangesBlock.call(); 67 | }).then(result => { 68 | exchangeRateChangesBlock = convertInt(result); 69 | return net.fundingEndBlock.call(); 70 | }).then(result => { 71 | fundingEndBlock = convertInt(result); 72 | return net.TOKEN_FIRST_EXCHANGE_RATE.call(); 73 | }).then(result => { 74 | tokenFirstExchangeRate = convertInt(result); 75 | return net.TOKEN_SECOND_EXCHANGE_RATE.call(); 76 | }).then(result => { 77 | tokenSecondExchangeRate = convertInt(result); 78 | return net.ETH_RECEIVED_CAP.call(); 79 | }).then(result => { 80 | ethReceivedCap = convertInt(result); 81 | return net.ETH_RECEIVED_MIN.call(); 82 | }).then(result => { 83 | ethReceivedMin = convertInt(result); 84 | return net.TOKEN_CREATION_CAP.call(); 85 | }).then(result => { 86 | tokenCreationCap = convertInt(result); 87 | return net.ethFundDeposit.call(); 88 | }).then(result => { 89 | ethFundDeposit = result; 90 | }); 91 | }); 92 | 93 | it('should start at block 4', function() { 94 | return NEToken.deployed().then(instance => { 95 | return blockNumber(); 96 | }).then(block => { 97 | assert.equal(block, 4, 'after deploying we should be at block 4, perhaps you should restart testrpc'); 98 | }); 99 | }); 100 | 101 | it('should have a fundingStartBlock in the future', function() { 102 | return blockNumber().then(currentBlock => { 103 | assert.ok(currentBlock < (fundingStartBlock - 1), 'fundingStartBlock is not in the future'); 104 | }); 105 | }); 106 | 107 | it('is not yet finalized/redeeming', function() { 108 | return NEToken.deployed().then(net => { 109 | return net.state.call(); 110 | }).then(state => { 111 | assert.ok(convertInt(state) === isFundraising, 'should not be finalized'); 112 | }) 113 | }); 114 | 115 | it('no finalization before the beginning', function() { 116 | let net = null; 117 | return NEToken.deployed().then(instance => { 118 | net = instance; 119 | return net.finalize({ 120 | from: ethFundDeposit, 121 | gas: 2099999, 122 | gasPrice: 20000000000 123 | }); 124 | }).then(() => { 125 | assert.fail('should not be possible to finalize'); 126 | }).catch(e => { 127 | if (e.name == 'Error') { 128 | assert.ok(true); 129 | } else { 130 | assert.fail('should not be possible to finalize'); 131 | } 132 | }); 133 | }); 134 | 135 | it('should not issue tokens before the beginning', function() { 136 | return NEToken.deployed().then(net => { 137 | return net.createTokens({ 138 | from: accounts[0], 139 | value: standardBid, 140 | gas: 2099999, 141 | gasPrice: 20000000000 142 | }); 143 | }).then(() => { 144 | assert.fail('token creation did not fail'); 145 | }).catch(e => { 146 | if (e.name == 'Error') { 147 | assert.ok(true); 148 | } else { 149 | assert.fail('token creation did not fail'); 150 | } 151 | }); 152 | }); 153 | 154 | it('should not allow tokens to be traded without having them', function() { 155 | return NEToken.deployed().then(net => { 156 | return net.transfer(accounts[1], 10, { 157 | from: accounts[0], 158 | gas: 2099999, 159 | gasPrice: 20000000000 160 | }); 161 | }).then(() => { 162 | assert.fail('trading did not fail'); 163 | }).catch(e => { 164 | if (e.name == 'Error') { 165 | assert.ok(true); 166 | } else { 167 | assert.fail('trading did not fail'); 168 | } 169 | }); 170 | }); 171 | 172 | it('take a snapshot of the blockchain', function() { 173 | return snapshot(); 174 | }); 175 | 176 | it('should issue tokens for the correct price in phase 1', function() { 177 | let net = null; 178 | const weis = standardBid; 179 | return NEToken.deployed().then(instance => { 180 | net = instance; 181 | return blockNumber(); 182 | }).then(currentBlock => { 183 | // mine necessary amount of blocks 184 | return mineBlocks(fundingStartBlock - currentBlock - 1); 185 | }).then(() => { 186 | return net.createTokens({ 187 | from: accounts[1], 188 | value: weis, 189 | gas: 2099999, 190 | gasPrice: 20000000000 191 | }); 192 | }).then(() => { 193 | return net.balanceOf.call(accounts[1]); 194 | }).then(balance => { 195 | assert.equal(balance, weis * tokenFirstExchangeRate, 'got wrong amount of tokens'); 196 | }).catch(() => { 197 | assert.fail('token creation did fail'); 198 | }); 199 | }); 200 | 201 | it('should not issue tokens that overshoot the eth received cap', function() { 202 | let net = null; 203 | const weis = ethReceivedCap; // definitely too much 204 | return NEToken.deployed().then(instance => { 205 | net = instance; 206 | return net.createTokens({ 207 | from: accounts[0], 208 | value: weis, 209 | gas: 2099999, 210 | gasPrice: 20000000000 211 | }); 212 | }).then(() => { 213 | assert.fail('should not allow token creation overshooting cap'); 214 | }).catch(() => { 215 | assert.ok(true); 216 | }); 217 | }); 218 | 219 | it('no finalization before the end when the cap conditions are not met', function() { 220 | let net = null; 221 | return NEToken.deployed().then(instance => { 222 | net = instance; 223 | return net.finalize({ 224 | from: ethFundDeposit, 225 | gas: 2099999, 226 | gasPrice: 20000000000 227 | }); 228 | }).then(() => { 229 | assert.fail('should not be possible to finalize'); 230 | }).catch(e => { 231 | if (e.name == 'Error') { 232 | assert.ok(true); 233 | } else { 234 | assert.fail('should not be possible to finalize'); 235 | } 236 | }); 237 | }); 238 | 239 | it('should issue tokens exactly matching eth cap', function() { 240 | let net = null; 241 | const weis = ethReceivedCap - standardBid; 242 | return NEToken.deployed().then(instance => { 243 | net = instance; 244 | return net.createTokens({ 245 | from: accounts[0], 246 | value: weis, 247 | gas: 2099999, 248 | gasPrice: 20000000000 249 | }); 250 | }).then(() => { 251 | return net.balanceOf.call(accounts[0]); 252 | }).then(balance => { 253 | assert.equal(balance, weis * tokenFirstExchangeRate, 'got wrong amount of tokens'); 254 | }).catch(() => { 255 | assert.fail('token creation did fail'); 256 | }); 257 | }); 258 | 259 | it('no refunding at this point', function() { 260 | let net = null; 261 | return NEToken.deployed().then(instance => { 262 | net = instance; 263 | return net.refund({ 264 | from: accounts[0], 265 | gas: 2099999, 266 | gasPrice: 20000000000 267 | }); 268 | }).then(() => { 269 | assert.fail('should not be possible to refund'); 270 | }).catch(e => { 271 | if (e.name == 'Error') { 272 | assert.ok(true); 273 | } else { 274 | assert.fail('should not be possible to refund'); 275 | } 276 | }); 277 | }); 278 | 279 | it('should not issue tokens after reaching the eth received cap', function() { 280 | let net = null; 281 | const weis = 1; 282 | return NEToken.deployed().then(instance => { 283 | net = instance; 284 | return net.createTokens({ 285 | from: accounts[2], 286 | value: weis, 287 | gas: 2099999, 288 | gasPrice: 20000000000 289 | }); 290 | }).then(() => { 291 | assert.fail('should not allow token creation overshooting cap'); 292 | }).catch(() => { 293 | assert.ok(true); 294 | }); 295 | }); 296 | 297 | it('should allow early finalization', function() { 298 | let net = null; 299 | const gasUsed = 34016; // calculated by running the transaction once 300 | const gasPrice = 20000000000; 301 | return NEToken.deployed().then(instance => { 302 | net = instance; 303 | return net.finalize({ 304 | from: ethFundDeposit, 305 | gas: 2099999, 306 | gasPrice: 20000000000 307 | }); 308 | }).then(() => { 309 | return net.state.call(); 310 | }).then(state => { 311 | assert.ok(convertInt(state) === isFinalized); 312 | return web3.eth.getBalance(ethFundDeposit); 313 | }).then(balance => { 314 | assert.ok(balance >= (ethReceivedCap+initialFundBalance-(gasUsed*gasPrice)), 'balance is not correctly updated'); 315 | }).catch(() => { 316 | assert.fail('could not finalize contract'); 317 | }); 318 | }); 319 | 320 | it('should allow tokens to be traded after finalization', function() { 321 | return NEToken.deployed().then(net => { 322 | return net.transfer(accounts[1], 10, { 323 | from: accounts[0], 324 | gas: 2099999, 325 | gasPrice: 20000000000 326 | }); 327 | }).then(() => { 328 | assert.ok(true); 329 | }).catch(() => { 330 | assert.fail('could not trade tokens'); 331 | }); 332 | }); 333 | }); 334 | -------------------------------------------------------------------------------- /test/TokenCap.js: -------------------------------------------------------------------------------- 1 | // Specifically request an abstraction for NEToken 2 | var NEToken = artifacts.require("NEToken"); 3 | 4 | async function mineBlocks(num=1) { 5 | for (let i=0; i { 62 | net = instance; 63 | return net.fundingStartBlock.call(); 64 | }).then(result => { 65 | fundingStartBlock = convertInt(result); 66 | return net.exchangeRateChangesBlock.call(); 67 | }).then(result => { 68 | exchangeRateChangesBlock = convertInt(result); 69 | return net.fundingEndBlock.call(); 70 | }).then(result => { 71 | fundingEndBlock = convertInt(result); 72 | return net.TOKEN_FIRST_EXCHANGE_RATE.call(); 73 | }).then(result => { 74 | tokenFirstExchangeRate = convertInt(result); 75 | return net.TOKEN_SECOND_EXCHANGE_RATE.call(); 76 | }).then(result => { 77 | tokenSecondExchangeRate = convertInt(result); 78 | return net.ETH_RECEIVED_CAP.call(); 79 | }).then(result => { 80 | ethReceivedCap = convertInt(result); 81 | return net.ETH_RECEIVED_MIN.call(); 82 | }).then(result => { 83 | ethReceivedMin = convertInt(result); 84 | return net.TOKEN_CREATION_CAP.call(); 85 | }).then(result => { 86 | tokenCreationCap = convertInt(result); 87 | return net.ethFundDeposit.call(); 88 | }).then(result => { 89 | ethFundDeposit = result; 90 | }); 91 | }); 92 | 93 | it('should start at block 4', function() { 94 | return NEToken.deployed().then(instance => { 95 | return blockNumber(); 96 | }).then(block => { 97 | assert.equal(block, 4, 'after deploying we should be at block 4, perhaps you should restart testrpc'); 98 | }); 99 | }); 100 | 101 | it('should have a fundingStartBlock in the future', function() { 102 | return blockNumber().then(currentBlock => { 103 | assert.ok(currentBlock < (fundingStartBlock - 1), 'fundingStartBlock is not in the future'); 104 | }); 105 | }); 106 | 107 | it('is not yet finalized/redeeming', function() { 108 | return NEToken.deployed().then(net => { 109 | return net.state.call(); 110 | }).then(state => { 111 | assert.ok(convertInt(state) === isFundraising, 'should not be finalized'); 112 | }) 113 | }); 114 | 115 | it('no finalization before the beginning', function() { 116 | let net = null; 117 | return NEToken.deployed().then(instance => { 118 | net = instance; 119 | return net.finalize({ 120 | from: ethFundDeposit, 121 | gas: 2099999, 122 | gasPrice: 20000000000 123 | }); 124 | }).then(() => { 125 | assert.fail('should not be possible to finalize'); 126 | }).catch(e => { 127 | if (e.name == 'Error') { 128 | assert.ok(true); 129 | } else { 130 | assert.fail('should not be possible to finalize'); 131 | } 132 | }); 133 | }); 134 | 135 | it('should not issue tokens before the beginning', function() { 136 | return NEToken.deployed().then(net => { 137 | return net.createTokens({ 138 | from: accounts[0], 139 | value: standardBid, 140 | gas: 2099999, 141 | gasPrice: 20000000000 142 | }); 143 | }).then(() => { 144 | assert.fail('token creation did not fail'); 145 | }).catch(e => { 146 | if (e.name == 'Error') { 147 | assert.ok(true); 148 | } else { 149 | assert.fail('token creation did not fail'); 150 | } 151 | }); 152 | }); 153 | 154 | it('should not allow tokens to be traded without having them', function() { 155 | return NEToken.deployed().then(net => { 156 | return net.transfer(accounts[1], 10, { 157 | from: accounts[0], 158 | gas: 2099999, 159 | gasPrice: 20000000000 160 | }); 161 | }).then(() => { 162 | assert.fail('trading did not fail'); 163 | }).catch(e => { 164 | if (e.name == 'Error') { 165 | assert.ok(true); 166 | } else { 167 | assert.fail('trading did not fail'); 168 | } 169 | }); 170 | }); 171 | 172 | it('take a snapshot of the blockchain', function() { 173 | return snapshot(); 174 | }); 175 | 176 | it('should issue tokens for the correct price in phase 1', function() { 177 | let net = null; 178 | const weis = standardBid; 179 | return NEToken.deployed().then(instance => { 180 | net = instance; 181 | return blockNumber(); 182 | }).then(currentBlock => { 183 | // mine necessary amount of blocks 184 | return mineBlocks(fundingStartBlock - currentBlock - 1); 185 | }).then(() => { 186 | return net.createTokens({ 187 | from: accounts[0], 188 | value: weis, 189 | gas: 2099999, 190 | gasPrice: 20000000000 191 | }); 192 | }).then(() => { 193 | return net.balanceOf.call(accounts[0]); 194 | }).then(balance => { 195 | assert.equal(balance, weis * tokenFirstExchangeRate, 'got wrong amount of tokens'); 196 | }).catch(() => { 197 | assert.fail('token creation did fail'); 198 | }); 199 | }); 200 | 201 | it('should not issue tokens that overshoot the token creation cap', function() { 202 | let net = null; 203 | const weis = 100000000000000000000000; // definitely too much 204 | return NEToken.deployed().then(instance => { 205 | net = instance; 206 | return net.createTokens({ 207 | from: accounts[0], 208 | value: weis, 209 | gas: 2099999, 210 | gasPrice: 20000000000 211 | }); 212 | }).then(() => { 213 | assert.fail('should not allow token creation overshooting cap'); 214 | }).catch(() => { 215 | assert.ok(true); 216 | }); 217 | }); 218 | 219 | it('no finalization before the end when the cap conditions are not met', function() { 220 | let net = null; 221 | return NEToken.deployed().then(instance => { 222 | net = instance; 223 | return net.finalize({ 224 | from: ethFundDeposit, 225 | gas: 2099999, 226 | gasPrice: 20000000000 227 | }); 228 | }).then(() => { 229 | assert.fail('should not be possible to finalize'); 230 | }).catch(e => { 231 | if (e.name == 'Error') { 232 | assert.ok(true); 233 | } else { 234 | assert.fail('should not be possible to finalize'); 235 | } 236 | }); 237 | }); 238 | 239 | it('should issue tokens exactly matching token cap', function() { 240 | let net = null; 241 | const weis = (tokenCreationCap/tokenFirstExchangeRate) - standardBid; 242 | 243 | if (weis > ethReceivedCap) { 244 | console.log('tokenCreationCap cannot be reached, since it would overshoot the ethReceivedCap'); 245 | return true; 246 | } 247 | 248 | return NEToken.deployed().then(instance => { 249 | net = instance; 250 | return net.createTokens({ 251 | from: accounts[0], 252 | value: weis, 253 | gas: 2099999, 254 | gasPrice: 20000000000 255 | }); 256 | }).then(() => { 257 | return net.balanceOf.call(accounts[0]); 258 | }).then(balance => { 259 | assert.equal(balance, tokenCreationCap, 'got wrong amount of tokens'); // this account bought all of it 260 | }).catch(e => { 261 | console.log(e); 262 | assert.fail('token creation did fail'); 263 | }); 264 | }); 265 | 266 | it('no refunding at this point', function() { 267 | let net = null; 268 | return NEToken.deployed().then(instance => { 269 | net = instance; 270 | return net.refund({ 271 | from: accounts[0], 272 | gas: 2099999, 273 | gasPrice: 20000000000 274 | }); 275 | }).then(() => { 276 | assert.fail('should not be possible to refund'); 277 | }).catch(e => { 278 | if (e.name == 'Error') { 279 | assert.ok(true); 280 | } else { 281 | assert.fail('should not be possible to refund'); 282 | } 283 | }); 284 | }); 285 | 286 | it('no redeeming period here', function() { 287 | let net = null; 288 | return NEToken.deployed().then(instance => { 289 | net = instance; 290 | return net.startRedeeming({ 291 | from: ethFundDeposit, 292 | gas: 2099999, 293 | gasPrice: 20000000000 294 | }); 295 | }).then(() => { 296 | assert.fail('should not be possible to start redeeming phase'); 297 | }).catch(e => { 298 | if (e.name == 'Error') { 299 | assert.ok(true); 300 | } else { 301 | assert.fail('should not be possible to start redeeming phase'); 302 | } 303 | }); 304 | }); 305 | 306 | it('should not issue tokens after reaching the token cap', function() { 307 | let net = null; 308 | const weis = 1; 309 | return NEToken.deployed().then(instance => { 310 | net = instance; 311 | return net.createTokens({ 312 | from: accounts[2], 313 | value: weis, 314 | gas: 2099999, 315 | gasPrice: 20000000000 316 | }); 317 | }).then(() => { 318 | assert.fail('should not allow token creation overshooting cap'); 319 | }).catch(() => { 320 | assert.ok(true); 321 | }); 322 | }); 323 | 324 | it('should allow early finalization', function() { 325 | const weis = (tokenCreationCap/tokenFirstExchangeRate)-standardBid; 326 | 327 | if (weis > ethReceivedCap) { 328 | console.log('tokenCreationCap cannot be reached, since it would overshoot the ethReceivedCap'); 329 | return true; 330 | } 331 | 332 | let net = null; 333 | const gasUsed = 34016; // calculated by running the transaction once 334 | const gasPrice = 20000000000; 335 | return NEToken.deployed().then(instance => { 336 | net = instance; 337 | return net.finalize({ 338 | from: ethFundDeposit, 339 | gas: 2099999, 340 | gasPrice: 20000000000 341 | }); 342 | }).then(() => { 343 | return net.state.call(); 344 | }).then(state => { 345 | assert.ok(convertInt(state) === isFinalized); 346 | return web3.eth.getBalance(ethFundDeposit); 347 | }).then(balance => { 348 | assert.ok(balance >= (initialFundBalance+tokenCreationCap/500-(gasUsed*gasPrice)), 'balance is not correctly updated'); 349 | }).catch(() => { 350 | assert.fail('could not finalize contract'); 351 | }); 352 | }); 353 | 354 | it('should allow tokens to be traded after finalization', function() { 355 | const weis = (tokenCreationCap/tokenFirstExchangeRate)-standardBid; 356 | 357 | if (weis > ethReceivedCap) { 358 | console.log('tokenCreationCap cannot be reached, since it would overshoot the ethReceivedCap'); 359 | return true; 360 | } 361 | 362 | return NEToken.deployed().then(net => { 363 | return net.transfer(accounts[1], 10, { 364 | from: accounts[0], 365 | gas: 2099999, 366 | gasPrice: 20000000000 367 | }); 368 | }).then(() => { 369 | assert.ok(true); 370 | }).catch(() => { 371 | assert.fail('could not trade tokens'); 372 | }); 373 | }); 374 | }); 375 | -------------------------------------------------------------------------------- /dist/NEToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | /* taking ideas from FirstBlood token */ 4 | contract SafeMath { 5 | 6 | function safeAdd(uint256 x, uint256 y) internal returns(uint256) { 7 | uint256 z = x + y; 8 | assert((z >= x) && (z >= y)); 9 | return z; 10 | } 11 | 12 | function safeSubtract(uint256 x, uint256 y) internal returns(uint256) { 13 | assert(x >= y); 14 | uint256 z = x - y; 15 | return z; 16 | } 17 | 18 | function safeMult(uint256 x, uint256 y) internal returns(uint256) { 19 | uint256 z = x * y; 20 | assert((x == 0)||(z/x == y)); 21 | return z; 22 | } 23 | } 24 | 25 | contract Token { 26 | uint256 public totalSupply; 27 | 28 | function balanceOf(address _owner) constant returns (uint256 balance); 29 | function transfer(address _to, uint256 _value) returns (bool success); 30 | function transferFrom(address _from, address _to, uint256 _value) returns (bool success); 31 | function approve(address _spender, uint256 _value) returns (bool success); 32 | function allowance(address _owner, address _spender) constant returns (uint256 remaining); 33 | 34 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 35 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 36 | } 37 | 38 | /* ERC 20 token */ 39 | contract StandardToken is Token, SafeMath { 40 | 41 | mapping (address => uint256) balances; 42 | mapping (address => mapping (address => uint256)) allowed; 43 | 44 | modifier onlyPayloadSize(uint numwords) { 45 | assert(msg.data.length == numwords * 32 + 4); 46 | _; 47 | } 48 | 49 | function transfer(address _to, uint256 _value) 50 | returns (bool success) 51 | { 52 | if (balances[msg.sender] >= _value && _value > 0 && balances[_to] + _value > balances[_to]) { 53 | balances[msg.sender] = safeSubtract(balances[msg.sender], _value); 54 | balances[_to] = safeAdd(balances[_to], _value); 55 | Transfer(msg.sender, _to, _value); 56 | return true; 57 | } else { 58 | return false; 59 | } 60 | } 61 | 62 | function transferFrom(address _from, address _to, uint256 _value) 63 | returns (bool success) 64 | { 65 | if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0 && balances[_to] + _value > balances[_to]) { 66 | balances[_to] = safeAdd(balances[_to], _value); 67 | balances[_from] = safeSubtract(balances[_from], _value); 68 | allowed[_from][msg.sender] = safeSubtract(allowed[_from][msg.sender], _value); 69 | Transfer(_from, _to, _value); 70 | return true; 71 | } else { 72 | return false; 73 | } 74 | } 75 | 76 | function balanceOf(address _owner) constant returns (uint256 balance) { 77 | return balances[_owner]; 78 | } 79 | 80 | function approve(address _spender, uint256 _value) 81 | onlyPayloadSize(2) 82 | returns (bool success) 83 | { 84 | allowed[msg.sender][_spender] = _value; 85 | Approval(msg.sender, _spender, _value); 86 | return true; 87 | } 88 | 89 | function allowance(address _owner, address _spender) 90 | constant 91 | onlyPayloadSize(2) 92 | returns (uint256 remaining) 93 | { 94 | return allowed[_owner][_spender]; 95 | } 96 | } 97 | 98 | /* Taking ideas from BAT token */ 99 | contract NEToken is StandardToken { 100 | 101 | // Token metadata 102 | string public constant name = "Nimiq Network Interim Token"; 103 | string public constant symbol = "NET"; 104 | uint256 public constant decimals = 18; 105 | string public version = "0.8"; 106 | 107 | // Deposit address of Multisig account controlled by the creators 108 | address public ethFundDeposit; 109 | 110 | // Fundraising parameters 111 | enum ContractState { Fundraising, Finalized, Redeeming, Paused } 112 | ContractState public state; // Current state of the contract 113 | ContractState private savedState; // State of the contract before pause 114 | 115 | uint256 public fundingStartBlock; // These two blocks need to be chosen to comply with the 116 | uint256 public fundingEndBlock; // start date and 28 day duration requirements 117 | uint256 public exchangeRateChangesBlock; // block number that triggers the exchange rate change 118 | 119 | uint256 public constant TOKEN_FIRST_EXCHANGE_RATE = 175; // 175 NETs per 1 ETH 120 | uint256 public constant TOKEN_SECOND_EXCHANGE_RATE = 125; // 125 NETs per 1 ETH 121 | uint256 public constant TOKEN_CREATION_CAP = 10.5 * (10**6) * 10**decimals; // 10.5 million NETs 122 | uint256 public constant ETH_RECEIVED_CAP = 60 * (10**3) * 10**decimals; // 60 000 ETH 123 | uint256 public constant ETH_RECEIVED_MIN = 5 * (10**3) * 10**decimals; // 5 000 ETH 124 | uint256 public constant TOKEN_MIN = 1 * 10**decimals; // 1 NET 125 | 126 | // We need to keep track of how much ether have been contributed, since we have a cap for ETH too 127 | uint256 public totalReceivedEth = 0; 128 | 129 | // Since we have different exchange rates at different stages, we need to keep track 130 | // of how much ether each contributed in case that we need to issue a refund 131 | mapping (address => uint256) private ethBalances; 132 | 133 | // Events used for logging 134 | event LogRefund(address indexed _to, uint256 _value); 135 | event LogCreateNET(address indexed _to, uint256 _value); 136 | event LogRedeemNET(address indexed _to, uint256 _value, bytes32 _nimiqAddress); 137 | 138 | modifier isFinalized() { 139 | require(state == ContractState.Finalized); 140 | _; 141 | } 142 | 143 | modifier isFundraising() { 144 | require(state == ContractState.Fundraising); 145 | _; 146 | } 147 | 148 | modifier isRedeeming() { 149 | require(state == ContractState.Redeeming); 150 | _; 151 | } 152 | 153 | modifier isPaused() { 154 | require(state == ContractState.Paused); 155 | _; 156 | } 157 | 158 | modifier notPaused() { 159 | require(state != ContractState.Paused); 160 | _; 161 | } 162 | 163 | modifier isFundraisingIgnorePaused() { 164 | require(state == ContractState.Fundraising || (state == ContractState.Paused && savedState == ContractState.Fundraising)); 165 | _; 166 | } 167 | 168 | modifier onlyOwner() { 169 | require(msg.sender == ethFundDeposit); 170 | _; 171 | } 172 | 173 | modifier minimumReached() { 174 | require(totalReceivedEth >= ETH_RECEIVED_MIN); 175 | _; 176 | } 177 | 178 | // Constructor 179 | function NEToken( 180 | address _ethFundDeposit, 181 | uint256 _fundingStartBlock, 182 | uint256 _fundingEndBlock, 183 | uint256 _exchangeRateChangesBlock) 184 | { 185 | // Check that the parameters make sense 186 | require(block.number <= _fundingStartBlock); // The start of the fundraising should happen in the future 187 | require(_fundingStartBlock <= _exchangeRateChangesBlock); // The exchange rate change should happen after the start of the fundraising 188 | require(_exchangeRateChangesBlock <= _fundingEndBlock); // And the end of the fundraising should happen after the exchange rate change 189 | 190 | // Contract state 191 | state = ContractState.Fundraising; 192 | savedState = ContractState.Fundraising; 193 | 194 | ethFundDeposit = _ethFundDeposit; 195 | fundingStartBlock = _fundingStartBlock; 196 | fundingEndBlock = _fundingEndBlock; 197 | exchangeRateChangesBlock = _exchangeRateChangesBlock; 198 | totalSupply = 0; 199 | } 200 | 201 | // Overridden method to check for end of fundraising before allowing transfer of tokens 202 | function transfer(address _to, uint256 _value) 203 | isFinalized // Only allow token transfer after the fundraising has ended 204 | onlyPayloadSize(2) 205 | returns (bool success) 206 | { 207 | return super.transfer(_to, _value); 208 | } 209 | 210 | 211 | // Overridden method to check for end of fundraising before allowing transfer of tokens 212 | function transferFrom(address _from, address _to, uint256 _value) 213 | isFinalized // Only allow token transfer after the fundraising has ended 214 | onlyPayloadSize(3) 215 | returns (bool success) 216 | { 217 | return super.transferFrom(_from, _to, _value); 218 | } 219 | 220 | 221 | /// @dev Accepts ether and creates new NET tokens 222 | function createTokens() 223 | payable 224 | external 225 | isFundraising 226 | { 227 | require(block.number >= fundingStartBlock); 228 | require(block.number <= fundingEndBlock); 229 | require(msg.value > 0); 230 | 231 | // First we check the ETH cap, as it's easier to calculate, return 232 | // the contribution if the cap has been reached already 233 | uint256 checkedReceivedEth = safeAdd(totalReceivedEth, msg.value); 234 | require(checkedReceivedEth <= ETH_RECEIVED_CAP); 235 | 236 | // If all is fine with the ETH cap, we continue to check the 237 | // minimum amount of tokens and the cap for how many tokens 238 | // have been generated so far 239 | uint256 tokens = safeMult(msg.value, getCurrentTokenPrice()); 240 | require(tokens >= TOKEN_MIN); 241 | uint256 checkedSupply = safeAdd(totalSupply, tokens); 242 | require(checkedSupply <= TOKEN_CREATION_CAP); 243 | 244 | // Only when all the checks have passed, then we update the state (ethBalances, 245 | // totalReceivedEth, totalSupply, and balances) of the contract 246 | ethBalances[msg.sender] = safeAdd(ethBalances[msg.sender], msg.value); 247 | totalReceivedEth = checkedReceivedEth; 248 | totalSupply = checkedSupply; 249 | balances[msg.sender] += tokens; // safeAdd not needed; bad semantics to use here 250 | 251 | // Log the creation of this tokens 252 | LogCreateNET(msg.sender, tokens); 253 | } 254 | 255 | 256 | /// @dev Returns the current token price 257 | function getCurrentTokenPrice() 258 | private 259 | constant 260 | returns (uint256 currentPrice) 261 | { 262 | if (block.number < exchangeRateChangesBlock) { 263 | return TOKEN_FIRST_EXCHANGE_RATE; 264 | } else { 265 | return TOKEN_SECOND_EXCHANGE_RATE; 266 | } 267 | } 268 | 269 | 270 | /// @dev Redeems NETs and records the Nimiq address of the sender 271 | function redeemTokens(bytes32 nimiqAddress) 272 | external 273 | isRedeeming 274 | { 275 | uint256 netVal = balances[msg.sender]; 276 | require(netVal >= TOKEN_MIN); // At least TOKEN_MIN tokens have to be redeemed 277 | 278 | // Move the tokens of the caller to Nimiq's address 279 | if (!super.transfer(ethFundDeposit, netVal)) throw; 280 | 281 | // Log the redeeming of this tokens 282 | LogRedeemNET(msg.sender, netVal, nimiqAddress); 283 | } 284 | 285 | 286 | /// @dev Allows to transfer ether from the contract as soon as the minimum is reached 287 | function retrieveEth(uint256 _value) 288 | external 289 | minimumReached 290 | onlyOwner 291 | { 292 | require(_value <= this.balance); 293 | 294 | // send the eth to Nimiq Creators 295 | ethFundDeposit.transfer(_value); 296 | } 297 | 298 | 299 | /// @dev Ends the fundraising period and sends the ETH to the Multisig wallet 300 | function finalize() 301 | external 302 | isFundraising 303 | minimumReached 304 | onlyOwner // Only the owner of the ethFundDeposit address can finalize the contract 305 | { 306 | require(block.number > fundingEndBlock || totalSupply >= TOKEN_CREATION_CAP || totalReceivedEth >= ETH_RECEIVED_CAP); // Only allow to finalize the contract before the ending block if we already reached any of the two caps 307 | 308 | // Move the contract to Finalized state 309 | state = ContractState.Finalized; 310 | savedState = ContractState.Finalized; 311 | 312 | // Send the ETH to Nimiq Creators 313 | ethFundDeposit.transfer(this.balance); 314 | } 315 | 316 | 317 | /// @dev Starts the redeeming period 318 | function startRedeeming() 319 | external 320 | isFinalized // The redeeming period can only be started after the contract is finalized 321 | onlyOwner // Only the owner of the ethFundDeposit address can start the redeeming period 322 | { 323 | // Move the contract to Redeeming state 324 | state = ContractState.Redeeming; 325 | savedState = ContractState.Redeeming; 326 | } 327 | 328 | 329 | /// @dev Pauses the contract 330 | function pause() 331 | external 332 | notPaused // Prevent the contract getting stuck in the Paused state 333 | onlyOwner // Only the owner of the ethFundDeposit address can pause the contract 334 | { 335 | // Move the contract to Paused state 336 | savedState = state; 337 | state = ContractState.Paused; 338 | } 339 | 340 | 341 | /// @dev Proceeds with the contract 342 | function proceed() 343 | external 344 | isPaused 345 | onlyOwner // Only the owner of the ethFundDeposit address can proceed with the contract 346 | { 347 | // Move the contract to the previous state 348 | state = savedState; 349 | } 350 | 351 | 352 | /// @dev Allows contributors to recover their ether in case the minimum funding goal is not reached 353 | function refund() 354 | external 355 | isFundraisingIgnorePaused // Refunding is only possible in the fundraising phase (no matter if paused) by definition 356 | { 357 | require(block.number > fundingEndBlock); // Prevents refund until fundraising period is over 358 | require(totalReceivedEth < ETH_RECEIVED_MIN); // No refunds if the minimum has been reached 359 | 360 | uint256 netVal = balances[msg.sender]; 361 | require(netVal > 0); 362 | uint256 ethVal = ethBalances[msg.sender]; 363 | require(ethVal > 0); 364 | 365 | // Update the state only after all the checks have passed 366 | balances[msg.sender] = 0; 367 | ethBalances[msg.sender] = 0; 368 | totalSupply = safeSubtract(totalSupply, netVal); // Extra safe 369 | 370 | // Log this refund 371 | LogRefund(msg.sender, ethVal); 372 | 373 | // Send the contributions only after we have updated all the balances 374 | // If you're using a contract, make sure it works with .transfer() gas limits 375 | msg.sender.transfer(ethVal); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /test/Refunding.js: -------------------------------------------------------------------------------- 1 | // Specifically request an abstraction for NEToken 2 | var NEToken = artifacts.require("NEToken"); 3 | 4 | async function mineBlocks(num=1) { 5 | for (let i=0; i { 79 | net = instance; 80 | return net.fundingStartBlock.call(); 81 | }).then(result => { 82 | fundingStartBlock = convertInt(result); 83 | return net.exchangeRateChangesBlock.call(); 84 | }).then(result => { 85 | exchangeRateChangesBlock = convertInt(result); 86 | return net.fundingEndBlock.call(); 87 | }).then(result => { 88 | fundingEndBlock = convertInt(result); 89 | return net.TOKEN_FIRST_EXCHANGE_RATE.call(); 90 | }).then(result => { 91 | tokenFirstExchangeRate = convertInt(result); 92 | return net.TOKEN_SECOND_EXCHANGE_RATE.call(); 93 | }).then(result => { 94 | tokenSecondExchangeRate = convertInt(result); 95 | return net.ETH_RECEIVED_CAP.call(); 96 | }).then(result => { 97 | ethReceivedCap = convertInt(result); 98 | return net.ETH_RECEIVED_MIN.call(); 99 | }).then(result => { 100 | ethReceivedMin = convertInt(result); 101 | return net.TOKEN_CREATION_CAP.call(); 102 | }).then(result => { 103 | tokenCreationCap = convertInt(result); 104 | return net.ethFundDeposit.call(); 105 | }).then(result => { 106 | ethFundDeposit = result; 107 | }); 108 | }); 109 | 110 | it('should start at block 4', function() { 111 | return NEToken.deployed().then(instance => { 112 | return blockNumber(); 113 | }).then(block => { 114 | assert.equal(block, 4, 'after deploying we should be at block 4, perhaps you should restart testrpc'); 115 | }); 116 | }); 117 | 118 | it('should have a fundingStartBlock in the future', function() { 119 | return blockNumber().then(currentBlock => { 120 | assert.ok(currentBlock < (fundingStartBlock - 1), 'fundingStartBlock is not in the future'); 121 | }); 122 | }); 123 | 124 | it('is not yet finalized/redeeming', function() { 125 | return NEToken.deployed().then(net => { 126 | return net.state.call(); 127 | }).then(state => { 128 | assert.ok(convertInt(state) === isFundraising, 'should not be finalized'); 129 | }) 130 | }); 131 | 132 | it('no refunding before the beginning', function() { 133 | let net = null; 134 | return NEToken.deployed().then(instance => { 135 | net = instance; 136 | return net.refund({ 137 | from: accounts[0], 138 | gas: 2099999, 139 | gasPrice: 20000000000 140 | }); 141 | }).then(() => { 142 | assert.fail('should not be possible to refund'); 143 | }).catch(e => { 144 | if (e.name == 'Error') { 145 | assert.ok(true); 146 | } else { 147 | assert.fail('should not be possible to refund'); 148 | } 149 | }); 150 | }); 151 | 152 | it('should not issue tokens before the beginning', function() { 153 | return NEToken.deployed().then(net => { 154 | return net.createTokens({ 155 | from: accounts[0], 156 | value: standardBid, 157 | gas: 2099999, 158 | gasPrice: 20000000000 159 | }); 160 | }).then(() => { 161 | assert.fail('token creation did not fail'); 162 | }).catch(e => { 163 | if (e.name == 'Error') { 164 | assert.ok(true); 165 | } else { 166 | assert.fail('token creation did not fail'); 167 | } 168 | }); 169 | }); 170 | 171 | it('should not allow tokens to be traded without having them', function() { 172 | return NEToken.deployed().then(net => { 173 | return net.transfer(accounts[1], 10, { 174 | from: accounts[0], 175 | gas: 2099999, 176 | gasPrice: 20000000000 177 | }); 178 | }).then(() => { 179 | assert.fail('trading did not fail'); 180 | }).catch(e => { 181 | if (e.name == 'Error') { 182 | assert.ok(true); 183 | } else { 184 | assert.fail('trading did not fail'); 185 | } 186 | }); 187 | }); 188 | 189 | it('take a snapshot of the blockchain', function() { 190 | return snapshot(); 191 | }); 192 | 193 | it('should issue tokens for the correct price in phase 1', function() { 194 | let net = null; 195 | const weis = standardBid; 196 | return NEToken.deployed().then(instance => { 197 | net = instance; 198 | return blockNumber(); 199 | }).then(currentBlock => { 200 | // mine necessary amount of blocks 201 | return mineBlocks(fundingStartBlock - currentBlock - 1); 202 | }).then(() => { 203 | return net.createTokens({ 204 | from: accounts[0], 205 | value: weis, 206 | gas: 2099999, 207 | gasPrice: 20000000000 208 | }); 209 | }).then(() => { 210 | return net.balanceOf.call(accounts[0]); 211 | }).then(balance => { 212 | assert.equal(balance, weis * tokenFirstExchangeRate, 'got wrong amount of tokens'); 213 | }).catch(() => { 214 | assert.fail('token creation did fail'); 215 | }); 216 | }); 217 | 218 | it('should not allow tokens to be traded during bidding in phase 1', function() { 219 | return NEToken.deployed().then(net => { 220 | return net.transfer(accounts[1], 10, { 221 | from: accounts[0], 222 | gas: 2099999, 223 | gasPrice: 20000000000 224 | }); 225 | }).then(() => { 226 | assert.fail('trading did not fail'); 227 | }).catch(e => { 228 | if (e.name == 'Error') { 229 | assert.ok(true); 230 | } else { 231 | assert.fail('trading did not fail'); 232 | } 233 | }); 234 | }); 235 | 236 | it('no refunding during the campaign', function() { 237 | let net = null; 238 | return NEToken.deployed().then(instance => { 239 | net = instance; 240 | return net.refund({ 241 | from: accounts[0], 242 | gas: 2099999, 243 | gasPrice: 20000000000 244 | }); 245 | }).then(() => { 246 | assert.fail('should not be possible to refund'); 247 | }).catch(e => { 248 | if (e.name == 'Error') { 249 | assert.ok(true); 250 | } else { 251 | assert.fail('should not be possible to refund'); 252 | } 253 | }); 254 | }); 255 | 256 | it('no finalization before the end when the cap conditions are not met', function() { 257 | let net = null; 258 | return NEToken.deployed().then(instance => { 259 | net = instance; 260 | return net.finalize({ 261 | from: ethFundDeposit, 262 | gas: 2099999, 263 | gasPrice: 20000000000 264 | }); 265 | }).then(() => { 266 | assert.fail('should not be possible to finalize'); 267 | }).catch(e => { 268 | if (e.name == 'Error') { 269 | assert.ok(true); 270 | } else { 271 | assert.fail('should not be possible to finalize'); 272 | } 273 | }); 274 | }); 275 | 276 | it('should not issue tokens after the funding has ended (because of reaching fundingEndBlock)', function() { 277 | let net = null; 278 | const weis = standardBid; 279 | return NEToken.deployed().then(instance => { 280 | net = instance; 281 | return blockNumber(); 282 | }).then(currentBlock => { 283 | // mine necessary amount of blocks 284 | return mineBlocks(fundingEndBlock - currentBlock); // we need to be > than fundingEndBlock 285 | }).then(() => { 286 | return net.createTokens({ 287 | from: accounts[2], 288 | value: weis, 289 | gas: 2099999, 290 | gasPrice: 20000000000 291 | }); 292 | }).then(balance => { 293 | assert.fail('token creation did not fail'); 294 | }).catch(e => { 295 | if (e.name == 'Error') { 296 | assert.ok(true); 297 | } else { 298 | assert.fail('token creation did not fail'); 299 | } 300 | }); 301 | }); 302 | 303 | it('should not allow tokens to be traded', function() { 304 | return NEToken.deployed().then(net => { 305 | return net.transfer(accounts[1], 10, { 306 | from: accounts[0], 307 | gas: 2099999, 308 | gasPrice: 20000000000 309 | }); 310 | }).then(() => { 311 | assert.fail('trading did not fail'); 312 | }).catch(e => { 313 | if (e.name == 'Error') { 314 | assert.ok(true); 315 | } else { 316 | assert.fail('trading did not fail'); 317 | } 318 | }); 319 | }); 320 | 321 | it('should not allow finalization (since below the ethReceivedMin)', function() { 322 | let net = null; 323 | return NEToken.deployed().then(instance => { 324 | net = instance; 325 | return net.finalize({ 326 | from: ethFundDeposit, 327 | gas: 2099999, 328 | gasPrice: 20000000000 329 | }); 330 | }).then(() => { 331 | assert.fail('finalization should not be allowed'); 332 | }).catch(e => { 333 | if (e.name == 'Error') { 334 | assert.ok(true); 335 | } else { 336 | assert.fail('finalization should not be allowed'); 337 | } 338 | }); 339 | }); 340 | 341 | it('should not allow tokens to be traded since funding goal has not been reached', function() { 342 | return NEToken.deployed().then(net => { 343 | return net.transfer(accounts[1], 10, { 344 | from: accounts[0], 345 | gas: 2099999, 346 | gasPrice: 20000000000 347 | }); 348 | }).then(() => { 349 | assert.fail('trading did not fail'); 350 | }).catch(e => { 351 | if (e.name == 'Error') { 352 | assert.ok(true); 353 | } else { 354 | assert.fail('trading did not fail'); 355 | } 356 | }); 357 | }); 358 | 359 | it('should allow refunding', function() { 360 | let net = null; 361 | let initialBalance = 0; 362 | let gasUsed = 0; 363 | const gasPrice = 20000000000; 364 | return NEToken.deployed().then(instance => { 365 | net = instance; 366 | return web3.eth.getBalance(accounts[0]); 367 | }).then(balance => { 368 | initialBalance = convertInt(balance); 369 | return net.refund({ 370 | from: accounts[0], 371 | gas: 2099999, 372 | gasPrice: 20000000000 373 | }); 374 | }).then(ret => { 375 | assert.ok(getEvent('LogRefund', ret), 'should log a Refund event'); 376 | return lastBlock(); 377 | }).then(lastBlock => { 378 | gasUsed = lastBlock.gasUsed + 1; // Add some extra gas to work around rounding errors 379 | return web3.eth.getBalance(accounts[0]); 380 | }).then(balance => { 381 | assert.ok(balance >= (initialBalance+standardBid-(gasUsed*gasPrice)), 'balance is not correctly updated'); 382 | }).catch(() => { 383 | assert.fail('could not refund'); 384 | }); 385 | }); 386 | 387 | it('should not allow to start redeeming period', function() { 388 | let net = null; 389 | return NEToken.deployed().then(instance => { 390 | net = instance; 391 | return net.startRedeeming({ 392 | from: ethFundDeposit, 393 | gas: 2099999, 394 | gasPrice: 20000000000 395 | }); 396 | }).then(() => { 397 | assert.fail('should not be possible to start redeeming phase'); 398 | }).catch(e => { 399 | if (e.name == 'Error') { 400 | assert.ok(true); 401 | } else { 402 | assert.fail('should not be possible to start redeeming phase'); 403 | } 404 | }); 405 | }); 406 | 407 | it('should not allow to redeem tokens', function() { 408 | let net = null; 409 | return NEToken.deployed().then(instance => { 410 | net = instance; 411 | return net.redeemTokens("0x3D7D9AF2BF88E91A9D73D10F79C278424DDC89D83D7D9AF2BF88E91A9D73D45A", { 412 | from: accounts[0], 413 | gas: 2099999, 414 | gasPrice: 20000000000 415 | }); 416 | }).then(() => { 417 | assert.fail('should not be possible to redeem'); 418 | }).catch(e => { 419 | if (e.name == 'Error') { 420 | assert.ok(true); 421 | } else { 422 | assert.fail('should not be possible to redeem'); 423 | } 424 | }); 425 | }); 426 | }); 427 | -------------------------------------------------------------------------------- /test/EthRetrieval.js: -------------------------------------------------------------------------------- 1 | // Specifically request an abstraction for NEToken 2 | var NEToken = artifacts.require("NEToken"); 3 | 4 | async function mineBlocks(num=1) { 5 | for (let i=0; i { 62 | net = instance; 63 | return net.fundingStartBlock.call(); 64 | }).then(result => { 65 | fundingStartBlock = convertInt(result); 66 | return net.exchangeRateChangesBlock.call(); 67 | }).then(result => { 68 | exchangeRateChangesBlock = convertInt(result); 69 | return net.fundingEndBlock.call(); 70 | }).then(result => { 71 | fundingEndBlock = convertInt(result); 72 | return net.TOKEN_FIRST_EXCHANGE_RATE.call(); 73 | }).then(result => { 74 | tokenFirstExchangeRate = convertInt(result); 75 | return net.TOKEN_SECOND_EXCHANGE_RATE.call(); 76 | }).then(result => { 77 | tokenSecondExchangeRate = convertInt(result); 78 | return net.ETH_RECEIVED_CAP.call(); 79 | }).then(result => { 80 | ethReceivedCap = convertInt(result); 81 | return net.ETH_RECEIVED_MIN.call(); 82 | }).then(result => { 83 | ethReceivedMin = convertInt(result); 84 | return net.TOKEN_CREATION_CAP.call(); 85 | }).then(result => { 86 | tokenCreationCap = convertInt(result); 87 | return net.ethFundDeposit.call(); 88 | }).then(result => { 89 | ethFundDeposit = result; 90 | }); 91 | }); 92 | 93 | it('should start at block 4', function() { 94 | return NEToken.deployed().then(instance => { 95 | return blockNumber(); 96 | }).then(block => { 97 | assert.equal(block, 4, 'after deploying we should be at block 4, perhaps you should restart testrpc'); 98 | }); 99 | }); 100 | 101 | it('should have a fundingStartBlock in the future', function() { 102 | return blockNumber().then(currentBlock => { 103 | assert.ok(currentBlock < (fundingStartBlock - 1), 'fundingStartBlock is not in the future'); 104 | }); 105 | }); 106 | 107 | it('is not yet finalized/redeeming', function() { 108 | return NEToken.deployed().then(net => { 109 | return net.state.call(); 110 | }).then(state => { 111 | assert.ok(convertInt(state) === isSale, 'should not be finalized'); 112 | }) 113 | }); 114 | 115 | it('no finalization before the beginning', function() { 116 | let net = null; 117 | return NEToken.deployed().then(instance => { 118 | net = instance; 119 | return net.finalize({ 120 | from: ethFundDeposit, 121 | gas: 2099999, 122 | gasPrice: 20000000000 123 | }); 124 | }).then(() => { 125 | assert.fail('should not be possible to finalize'); 126 | }).catch(e => { 127 | if (e.name === 'Error') { 128 | assert.ok(true); 129 | } else { 130 | assert.fail('should not be possible to finalize'); 131 | } 132 | }); 133 | }); 134 | 135 | it('should not issue tokens before the beginning', function() { 136 | return NEToken.deployed().then(net => { 137 | return net.createTokens({ 138 | from: accounts[0], 139 | value: standardBid, 140 | gas: 2099999, 141 | gasPrice: 20000000000 142 | }); 143 | }).then(() => { 144 | assert.fail('token creation did not fail'); 145 | }).catch(e => { 146 | if (e.name === 'Error') { 147 | assert.ok(true); 148 | } else { 149 | assert.fail('token creation did not fail'); 150 | } 151 | }); 152 | }); 153 | 154 | it('should not allow tokens to be traded without having them', function() { 155 | return NEToken.deployed().then(net => { 156 | return net.transfer(accounts[1], 10, { 157 | from: accounts[0], 158 | gas: 2099999, 159 | gasPrice: 20000000000 160 | }); 161 | }).then(() => { 162 | assert.fail('trading did not fail'); 163 | }).catch(e => { 164 | if (e.name === 'Error') { 165 | assert.ok(true); 166 | } else { 167 | assert.fail('trading did not fail'); 168 | } 169 | }); 170 | }); 171 | 172 | it('take a snapshot of the blockchain', function() { 173 | return snapshot(); 174 | }); 175 | 176 | 177 | it('ethFundDeposit should have the initialFundBalance', function() { 178 | return NEToken.deployed() 179 | .then(() => { 180 | return web3.eth.getBalance(ethFundDeposit); 181 | }).then(balance => { 182 | assert.equal(balance, initialFundBalance, 'ethFundDeposit wrong initial balance'); 183 | }).catch(() => { 184 | assert.fail('could not get balance of ethFundDeposit'); 185 | }); 186 | }); 187 | 188 | it('should issue tokens for the correct price in phase 1', function() { 189 | let net = null; 190 | const weis = standardBid; 191 | return NEToken.deployed().then(instance => { 192 | net = instance; 193 | return blockNumber(); 194 | }).then(currentBlock => { 195 | // mine necessary amount of blocks 196 | return mineBlocks(fundingStartBlock - currentBlock - 1); 197 | }).then(() => { 198 | return net.createTokens({ 199 | from: accounts[1], 200 | value: weis, 201 | gas: 2099999, 202 | gasPrice: 20000000000 203 | }); 204 | }).then(() => { 205 | return net.balanceOf.call(accounts[1]); 206 | }).then(balance => { 207 | assert.equal(balance, weis * tokenFirstExchangeRate, 'got wrong amount of tokens'); 208 | }).catch(() => { 209 | assert.fail('token creation did fail'); 210 | }); 211 | }); 212 | 213 | it('should not issue tokens that overshoot the eth received cap', function() { 214 | let net = null; 215 | const weis = ethReceivedCap; // definitely too much 216 | return NEToken.deployed().then(instance => { 217 | net = instance; 218 | return net.createTokens({ 219 | from: accounts[0], 220 | value: weis, 221 | gas: 2099999, 222 | gasPrice: 20000000000 223 | }); 224 | }).then(() => { 225 | assert.fail('should not allow token creation overshooting cap'); 226 | }).catch(() => { 227 | assert.ok(true); 228 | }); 229 | }); 230 | 231 | it('no finalization before the end when the cap conditions are not met', function() { 232 | let net = null; 233 | return NEToken.deployed().then(instance => { 234 | net = instance; 235 | return net.finalize({ 236 | from: ethFundDeposit, 237 | gas: 2099999, 238 | gasPrice: 20000000000 239 | }); 240 | }).then(() => { 241 | assert.fail('should not be possible to finalize'); 242 | }).catch(e => { 243 | if (e.name === 'Error') { 244 | assert.ok(true); 245 | } else { 246 | assert.fail('should not be possible to finalize'); 247 | } 248 | }); 249 | }); 250 | 251 | it('should not allow ether retrieval before minimum is raised', function() { 252 | let net = null; 253 | return NEToken.deployed().then(instance => { 254 | net = instance; 255 | return net.retrieveEth({ 256 | from: accounts[0], 257 | gas: 2099999, 258 | gasPrice: 20000000000 259 | }); 260 | }).then(() => { 261 | assert.fail('should not allow ether retrieval before minimum is raised'); 262 | }).catch(() => { 263 | assert.ok(true); 264 | }); 265 | }); 266 | 267 | it('should issue tokens correctly', function() { 268 | let net = null; 269 | const weis = ethReceivedMin; 270 | 271 | return NEToken.deployed().then(instance => { 272 | net = instance; 273 | return net.createTokens({ 274 | from: accounts[2], 275 | value: weis, 276 | gas: 2099999, 277 | gasPrice: 20000000000 278 | }); 279 | }).then(() => { 280 | return net.balanceOf.call(accounts[2]); 281 | }).then(balance => { 282 | assert.equal(balance, weis * tokenFirstExchangeRate, 'got wrong amount of tokens'); 283 | }).catch(() => { 284 | assert.fail('token creation did fail'); 285 | }); 286 | }); 287 | 288 | it('no refunding at this point', function() { 289 | let net = null; 290 | return NEToken.deployed().then(instance => { 291 | net = instance; 292 | return net.refund({ 293 | from: accounts[2], 294 | gas: 2099999, 295 | gasPrice: 20000000000 296 | }); 297 | }).then(() => { 298 | assert.fail('should not be possible to refund'); 299 | }).catch(e => { 300 | if (e.name === 'Error') { 301 | assert.ok(true); 302 | } else { 303 | assert.fail('should not be possible to refund'); 304 | } 305 | }); 306 | }); 307 | 308 | it('ethFundDeposit should have the initialFundBalance minus used gas', function() { 309 | const gasUsed = 2099999; 310 | const gasPrice = 20000000000; 311 | 312 | return NEToken.deployed() 313 | .then(() => { 314 | return web3.eth.getBalance(ethFundDeposit); 315 | }).then(balance => { 316 | assert.ok(balance >= initialFundBalance - (gasUsed * gasPrice), 'ethFundDeposit wrong initial balance'); 317 | }).catch(() => { 318 | assert.fail('could not get balance of ethFundDeposit'); 319 | }); 320 | }); 321 | 322 | it('should allow ether retrieval after minimum is raised', function() { 323 | let net = null; 324 | const gasUsed = 2129883; // calculated by running the transaction once 325 | const gasPrice = 20000000000; 326 | const ethValue = ethReceivedMin; 327 | 328 | return NEToken.deployed().then(instance => { 329 | net = instance; 330 | return net.retrieveEth(ethValue, { 331 | from: ethFundDeposit, 332 | gas: 2099999, 333 | gasPrice: 20000000000 334 | }); 335 | }).then(() => { 336 | return web3.eth.getBalance(ethFundDeposit); 337 | }).then(balance => { 338 | const expected = ethValue + initialFundBalance - (gasUsed * gasPrice); 339 | assert.ok(balance >= expected, 'balance is not correctly updated'); 340 | }).catch(() => { 341 | assert.fail('failed to retrieve eth'); 342 | }); 343 | }); 344 | 345 | it('should allow ether retrieval after minimum is raised', function() { 346 | let net = null; 347 | const gasUsed = 2129883 + 29756; // calculated by running the transaction once 348 | const gasPrice = 20000000000; 349 | const ethValue = standardBid; 350 | 351 | return NEToken.deployed().then(instance => { 352 | net = instance; 353 | return net.retrieveEth(ethValue, { 354 | from: ethFundDeposit, 355 | gas: 2099999, 356 | gasPrice: 20000000000 357 | }); 358 | }).then(() => { 359 | return web3.eth.getBalance(ethFundDeposit); 360 | }).then(balance => { 361 | const expected = ethValue + ethReceivedMin + initialFundBalance - (gasUsed * gasPrice); 362 | assert.ok(balance >= expected, 'balance is not correctly updated'); 363 | }).catch(() => { 364 | assert.fail('failed to retrieve eth'); 365 | }); 366 | }); 367 | 368 | it('should issue tokens correctly', function() { 369 | let net = null; 370 | const weis = standardBid; 371 | 372 | return NEToken.deployed().then(instance => { 373 | net = instance; 374 | return net.createTokens({ 375 | from: accounts[3], 376 | value: weis, 377 | gas: 2099999, 378 | gasPrice: 20000000000 379 | }); 380 | }).then(() => { 381 | return net.balanceOf.call(accounts[3]); 382 | }).then(balance => { 383 | assert.equal(balance, weis * tokenSecondExchangeRate, 'got wrong amount of tokens'); 384 | }).catch(() => { 385 | assert.fail('token creation did fail'); 386 | }); 387 | }); 388 | 389 | it('should allow ether retrieval after minimum is raised', function() { 390 | let net = null; 391 | const gasUsed = 2158273 + 31122; // calculated by running the transaction once 392 | const gasPrice = 20000000000; 393 | const ethValue = standardBid; 394 | 395 | return NEToken.deployed().then(instance => { 396 | net = instance; 397 | return net.retrieveEth(ethValue, { 398 | from: ethFundDeposit, 399 | gas: 2099999, 400 | gasPrice: 20000000000 401 | }); 402 | }).then(() => { 403 | return web3.eth.getBalance(ethFundDeposit); 404 | }).then(balance => { 405 | const expected = ethReceivedMin + 2 * standardBid + initialFundBalance - (gasUsed * gasPrice); 406 | assert.ok(balance >= expected, 'balance is not correctly updated'); 407 | }).catch(() => { 408 | assert.fail('failed to retrieve eth'); 409 | }); 410 | }); 411 | 412 | it('should issue tokens correctly', function() { 413 | let net = null; 414 | const weis = ethReceivedCap - ethReceivedMin - 2 * standardBid; 415 | 416 | return NEToken.deployed().then(instance => { 417 | net = instance; 418 | return net.createTokens({ 419 | from: accounts[0], 420 | value: weis, 421 | gas: 2099999, 422 | gasPrice: 20000000000 423 | }); 424 | }).then(() => { 425 | return net.balanceOf.call(accounts[0]); 426 | }).then(balance => { 427 | assert.equal(balance, weis * tokenSecondExchangeRate, 'got wrong amount of tokens'); 428 | }).catch(() => { 429 | assert.fail('token creation did fail'); 430 | }); 431 | }); 432 | 433 | it('should allow ether retrieval after minimum is raised', function() { 434 | let net = null; 435 | const gasUsed = 2187410 + 31869; // calculated by running the transaction once 436 | const gasPrice = 20000000000; 437 | const ethValue = ethReceivedCap - ethReceivedMin - 2 * standardBid; 438 | 439 | return NEToken.deployed().then(instance => { 440 | net = instance; 441 | return net.retrieveEth(ethValue, { 442 | from: ethFundDeposit, 443 | gas: 2099999, 444 | gasPrice: 20000000000 445 | }); 446 | }).then(() => { 447 | return web3.eth.getBalance(ethFundDeposit); 448 | }).then(balance => { 449 | const expected = ethReceivedCap + initialFundBalance - (gasUsed * gasPrice); 450 | assert.ok(balance >= expected, 'balance is not correctly updated'); 451 | }).catch(() => { 452 | assert.fail('failed to retrieve eth'); 453 | }); 454 | }); 455 | 456 | it('should not allow retrieval if funds are insufficient', function() { 457 | let net = null; 458 | return NEToken.deployed().then(instance => { 459 | net = instance; 460 | return net.retrieveEth(1, { 461 | from: ethFundDeposit, 462 | gas: 2099999, 463 | gasPrice: 20000000000 464 | }); 465 | }).then(() => { 466 | assert.fail('should not be possible to retrieve eth'); 467 | }).catch(e => { 468 | if (e.name === 'Error') { 469 | assert.ok(true); 470 | } else { 471 | assert.fail('should not be possible to retrieve eth'); 472 | } 473 | }); 474 | }); 475 | 476 | it('no refunding at this point', function() { 477 | let net = null; 478 | return NEToken.deployed().then(instance => { 479 | net = instance; 480 | return net.refund({ 481 | from: accounts[0], 482 | gas: 2099999, 483 | gasPrice: 20000000000 484 | }); 485 | }).then(() => { 486 | assert.fail('should not be possible to refund'); 487 | }).catch(e => { 488 | if (e.name === 'Error') { 489 | assert.ok(true); 490 | } else { 491 | assert.fail('should not be possible to refund'); 492 | } 493 | }); 494 | }); 495 | 496 | it('should not issue tokens after reaching the eth received cap', function() { 497 | let net = null; 498 | const weis = 1; 499 | return NEToken.deployed().then(instance => { 500 | net = instance; 501 | return net.createTokens({ 502 | from: accounts[2], 503 | value: weis, 504 | gas: 2099999, 505 | gasPrice: 20000000000 506 | }); 507 | }).then(() => { 508 | assert.fail('should not allow token creation overshooting cap'); 509 | }).catch(() => { 510 | assert.ok(true); 511 | }); 512 | }); 513 | 514 | it('should not allow tokens to be traded before finalization', function() { 515 | return NEToken.deployed().then(net => { 516 | return net.transfer(accounts[1], 10, { 517 | from: accounts[0], 518 | gas: 2099999, 519 | gasPrice: 20000000000 520 | }); 521 | }).then(() => { 522 | assert.fail('trading did not fail'); 523 | }).catch(() => { 524 | assert.ok(true); 525 | }); 526 | }); 527 | 528 | it('should allow early finalization', function() { 529 | let net = null; 530 | const gasUsed = 4320053 + 31869; // calculated by running the transaction once 531 | const gasPrice = 20000000000; 532 | 533 | return NEToken.deployed().then(instance => { 534 | net = instance; 535 | return net.finalize({ 536 | from: ethFundDeposit, 537 | gas: 2099999, 538 | gasPrice: 20000000000 539 | }); 540 | }).then(() => { 541 | return net.state.call(); 542 | }).then(state => { 543 | assert.ok(convertInt(state) === isFinalized); 544 | return web3.eth.getBalance(ethFundDeposit); 545 | }).then(balance => { 546 | const expected = ethReceivedCap + initialFundBalance - (gasUsed * gasPrice); 547 | assert.ok(balance >= expected, 'balance is not correctly updated'); 548 | }).catch(() => { 549 | assert.fail('could not finalize contract'); 550 | }); 551 | }); 552 | 553 | it('should allow tokens to be traded after finalization', function() { 554 | return NEToken.deployed().then(net => { 555 | return net.transfer(accounts[3], 10, { 556 | from: accounts[0], 557 | gas: 2099999, 558 | gasPrice: 20000000000 559 | }); 560 | }).then(() => { 561 | assert.ok(true); 562 | }).catch(() => { 563 | assert.fail('could not trade tokens'); 564 | }); 565 | }); 566 | 567 | it("should fail when trying to transfer too many tokens", function(done) { 568 | NEToken.deployed().then(net => { 569 | return net.transfer.call(accounts[0], 10, { 570 | from: accounts[4], 571 | gas: 2099999, 572 | gasPrice: 20000000000 573 | }); 574 | }).then(function (result) { 575 | assert.isFalse(result); 576 | done(); 577 | }).catch(done); 578 | }); 579 | 580 | it("should fail when trying to transfer zero", function(done) { 581 | NEToken.deployed().then(net => { 582 | return net.transfer.call(accounts[0], 0, { 583 | from: accounts[3], 584 | gas: 2099999, 585 | gasPrice: 20000000000 586 | }); 587 | }).then(function (result) { 588 | assert.isFalse(result); 589 | done(); 590 | }).catch(done); 591 | }); 592 | 593 | it("should approve 10 to accounts[1]", function(done) { 594 | let net = null; 595 | NEToken.deployed().then(instance => { 596 | net = instance; 597 | return net.approve(accounts[4], 10, { 598 | from: accounts[3], 599 | gas: 2099999, 600 | gasPrice: 20000000000 601 | }); 602 | }).then(function (result) { 603 | return net.allowance.call(accounts[3], accounts[4]); 604 | }).then(function (result) { 605 | assert.strictEqual(result.toNumber(), 10); 606 | done(); 607 | }).catch(done); 608 | }); 609 | 610 | it("transferFrom works", function(done) { 611 | let net = null; 612 | NEToken.deployed().then(instance => { 613 | net = instance; 614 | return net.transferFrom(accounts[3], accounts[4], 5, { 615 | from: accounts[4], 616 | gas: 2099999, 617 | gasPrice: 20000000000 618 | }); 619 | }).then(function (result) { 620 | return net.allowance.call(accounts[3], accounts[4]); 621 | }).then(function (result) { 622 | assert.strictEqual(result.toNumber(), 5); 623 | return net.balanceOf.call(accounts[4]); 624 | }).then(function (result) { 625 | assert.strictEqual(result.toNumber(), 5); 626 | done(); 627 | }).catch(done); 628 | }); 629 | 630 | it("fails for transfer from account with no allowance", function(done) { 631 | let net = null; 632 | NEToken.deployed().then(instance => { 633 | net = instance; 634 | return net.transferFrom.call(accounts[3], accounts[4], 5, { 635 | from: accounts[2], 636 | gas: 2099999, 637 | gasPrice: 20000000000 638 | }); 639 | }).then(function (result) { 640 | assert.isFalse(result); 641 | done(); 642 | }).catch(done); 643 | }); 644 | 645 | it("fails for transfer from account with not enough allowance", function(done) { 646 | let net = null; 647 | NEToken.deployed().then(instance => { 648 | net = instance; 649 | return net.transferFrom.call(accounts[3], accounts[4], 6, { 650 | from: accounts[4], 651 | gas: 2099999, 652 | gasPrice: 20000000000 653 | }); 654 | }).then(function (result) { 655 | assert.isFalse(result); 656 | done(); 657 | }).catch(done); 658 | }); 659 | }); 660 | -------------------------------------------------------------------------------- /test/NEToken.js: -------------------------------------------------------------------------------- 1 | // Specifically request an abstraction for NEToken 2 | var NEToken = artifacts.require("NEToken"); 3 | 4 | async function mineBlocks(num=1) { 5 | for (let i=0; i { 74 | net = instance; 75 | return net.fundingStartBlock.call(); 76 | }).then(result => { 77 | fundingStartBlock = convertInt(result); 78 | return net.exchangeRateChangesBlock.call(); 79 | }).then(result => { 80 | exchangeRateChangesBlock = convertInt(result); 81 | return net.fundingEndBlock.call(); 82 | }).then(result => { 83 | fundingEndBlock = convertInt(result); 84 | return net.TOKEN_FIRST_EXCHANGE_RATE.call(); 85 | }).then(result => { 86 | tokenFirstExchangeRate = convertInt(result); 87 | return net.TOKEN_SECOND_EXCHANGE_RATE.call(); 88 | }).then(result => { 89 | tokenSecondExchangeRate = convertInt(result); 90 | return net.ETH_RECEIVED_CAP.call(); 91 | }).then(result => { 92 | ethReceivedCap = convertInt(result); 93 | return net.ETH_RECEIVED_MIN.call(); 94 | }).then(result => { 95 | ethReceivedMin = convertInt(result); 96 | return net.TOKEN_CREATION_CAP.call(); 97 | }).then(result => { 98 | tokenCreationCap = convertInt(result); 99 | return net.TOKEN_MIN.call(); 100 | }).then(result => { 101 | tokenMin = convertInt(result); 102 | return net.ethFundDeposit.call(); 103 | }).then(result => { 104 | ethFundDeposit = result; 105 | }); 106 | }); 107 | 108 | it('should start at block 4', function() { 109 | return NEToken.deployed().then(instance => { 110 | return blockNumber(); 111 | }).then(block => { 112 | assert.equal(block, 4, 'after deploying we should be at block 4, perhaps you should restart testrpc'); 113 | }); 114 | }); 115 | 116 | it('should have a fundingStartBlock in the future', function() { 117 | return blockNumber().then(currentBlock => { 118 | assert.ok(currentBlock < (fundingStartBlock - 1), 'fundingStartBlock is not in the future'); 119 | }); 120 | }); 121 | 122 | it('is not yet finalized/redeeming', function() { 123 | return NEToken.deployed().then(net => { 124 | return net.state.call(); 125 | }).then(state => { 126 | assert.ok(convertInt(state) === isFundraising, 'should not be finalized'); 127 | }) 128 | }); 129 | 130 | it('no finalization before the beginning', function() { 131 | let net = null; 132 | return NEToken.deployed().then(instance => { 133 | net = instance; 134 | return net.finalize({ 135 | from: ethFundDeposit, 136 | gas: 2099999, 137 | gasPrice: 20000000000 138 | }); 139 | }).then(() => { 140 | assert.fail('should not be possible to finalize'); 141 | }).catch(e => { 142 | if (e.name == 'Error') { 143 | assert.ok(true); 144 | } else { 145 | assert.fail('should not be possible to finalize'); 146 | } 147 | }); 148 | }); 149 | 150 | it('no redeeming period before the beginning', function() { 151 | let net = null; 152 | return NEToken.deployed().then(instance => { 153 | net = instance; 154 | return net.startRedeeming({ 155 | from: ethFundDeposit, 156 | gas: 2099999, 157 | gasPrice: 20000000000 158 | }); 159 | }).then(() => { 160 | assert.fail('should not be possible to start redeeming phase'); 161 | }).catch(e => { 162 | if (e.name == 'Error') { 163 | assert.ok(true); 164 | } else { 165 | assert.fail('should not be possible to start redeeming phase'); 166 | } 167 | }); 168 | }); 169 | 170 | it('should not issue tokens before the beginning', function() { 171 | return NEToken.deployed().then(net => { 172 | return net.createTokens({ 173 | from: accounts[0], 174 | value: standardBid, 175 | gas: 2099999, 176 | gasPrice: 20000000000 177 | }); 178 | }).then(() => { 179 | assert.fail('token creation did not fail'); 180 | }).catch(e => { 181 | if (e.name == 'Error') { 182 | assert.ok(true); 183 | } else { 184 | assert.fail('token creation did not fail'); 185 | } 186 | }); 187 | }); 188 | 189 | it('should not allow tokens to be traded without having them', function() { 190 | return NEToken.deployed().then(net => { 191 | return net.transfer(accounts[1], 10, { 192 | from: accounts[0], 193 | gas: 2099999, 194 | gasPrice: 20000000000 195 | }); 196 | }).then(() => { 197 | assert.fail('trading did not fail'); 198 | }).catch(e => { 199 | if (e.name == 'Error') { 200 | assert.ok(true); 201 | } else { 202 | assert.fail('trading did not fail'); 203 | } 204 | }); 205 | }); 206 | 207 | it('take a snapshot of the blockchain', function() { 208 | return snapshot(); 209 | }); 210 | 211 | it('should issue tokens for the correct price in phase 1', function() { 212 | let net = null; 213 | const weis = standardBid; 214 | return NEToken.deployed().then(instance => { 215 | net = instance; 216 | return blockNumber(); 217 | }).then(currentBlock => { 218 | // mine necessary amount of blocks 219 | return mineBlocks(fundingStartBlock - currentBlock - 1); 220 | }).then(() => { 221 | return net.createTokens({ 222 | from: accounts[0], 223 | value: weis, 224 | gas: 2099999, 225 | gasPrice: 20000000000 226 | }); 227 | }).then(() => { 228 | return net.balanceOf.call(accounts[0]); 229 | }).then(balance => { 230 | assert.equal(balance, weis * tokenFirstExchangeRate, 'got wrong amount of tokens'); 231 | }).catch(() => { 232 | assert.fail('token creation did fail'); 233 | }); 234 | }); 235 | 236 | it('can pause while in fundraising', function() { 237 | let net = null; 238 | return NEToken.deployed().then(instance => { 239 | net = instance; 240 | return net.pause({ 241 | from: ethFundDeposit, 242 | gas: 2099999, 243 | gasPrice: 20000000000 244 | }); 245 | }).then(() => { 246 | return net.state.call(); 247 | }).then(state => { 248 | assert.ok(convertInt(state) === isPaused, 'should be paused'); 249 | }) 250 | }); 251 | 252 | it('should not issue tokens while paused', function() { 253 | return NEToken.deployed().then(net => { 254 | return net.createTokens({ 255 | from: accounts[0], 256 | value: standardBid, 257 | gas: 2099999, 258 | gasPrice: 20000000000 259 | }); 260 | }).then(() => { 261 | assert.fail('token creation did not fail'); 262 | }).catch(e => { 263 | if (e.name == 'Error') { 264 | assert.ok(true); 265 | } else { 266 | assert.fail('token creation did not fail'); 267 | } 268 | }); 269 | }); 270 | 271 | it('can proceed to fundraising', function() { 272 | let net = null; 273 | return NEToken.deployed().then(instance => { 274 | net = instance; 275 | return net.proceed({ 276 | from: ethFundDeposit, 277 | gas: 2099999, 278 | gasPrice: 20000000000 279 | }); 280 | }).then(() => { 281 | return net.state.call(); 282 | }).then(state => { 283 | assert.ok(convertInt(state) === isFundraising, 'should be proceeded'); 284 | }) 285 | }); 286 | 287 | it('should not issue tokens below the token minimum', function() { 288 | return NEToken.deployed().then(net => { 289 | return net.createTokens({ 290 | from: accounts[0], 291 | value: ((tokenMin-1) / tokenFirstExchangeRate), 292 | gas: 2099999, 293 | gasPrice: 20000000000 294 | }); 295 | }).then(() => { 296 | assert.fail('token creation did not fail'); 297 | }).catch(e => { 298 | if (e.name == 'Error') { 299 | assert.ok(true); 300 | } else { 301 | assert.fail('token creation did not fail'); 302 | } 303 | }); 304 | }); 305 | 306 | it('should not allow tokens to be traded during bidding in phase 1', function() { 307 | return NEToken.deployed().then(net => { 308 | return net.transfer(accounts[1], 10, { 309 | from: accounts[0], 310 | gas: 2099999, 311 | gasPrice: 20000000000 312 | }); 313 | }).then(() => { 314 | assert.fail('trading did not fail'); 315 | }).catch(e => { 316 | if (e.name == 'Error') { 317 | assert.ok(true); 318 | } else { 319 | assert.fail('trading did not fail'); 320 | } 321 | }); 322 | }); 323 | 324 | it('should issue tokens for the correct price in phase 2', function() { 325 | let net = null; 326 | const weis = standardBid; 327 | return NEToken.deployed().then(instance => { 328 | net = instance; 329 | return blockNumber(); 330 | }).then(currentBlock => { 331 | // mine necessary amount of blocks 332 | return mineBlocks(exchangeRateChangesBlock - currentBlock - 1); 333 | }).then(() => { 334 | return net.createTokens({ 335 | from: accounts[1], 336 | value: weis, 337 | gas: 2099999, 338 | gasPrice: 20000000000 339 | }); 340 | }).then(() => { 341 | return net.balanceOf.call(accounts[1]); 342 | }).then(balance => { 343 | assert.equal(balance, weis * tokenSecondExchangeRate, 'got wrong amount of tokens'); 344 | }).catch(() => { 345 | assert.fail('token creation did fail'); 346 | }); 347 | }); 348 | 349 | it('should issue more tokens for the correct price in phase 2', function() { 350 | let net = null; 351 | const weis = ethReceivedMin; 352 | return NEToken.deployed().then(instance => { 353 | net = instance; 354 | return net.createTokens({ 355 | from: accounts[2], 356 | value: weis, 357 | gas: 2099999, 358 | gasPrice: 20000000000 359 | }); 360 | }).then(() => { 361 | return net.balanceOf.call(accounts[2]); 362 | }).then(balance => { 363 | assert.equal(balance, weis * tokenSecondExchangeRate, 'got wrong amount of tokens'); 364 | }).catch(() => { 365 | assert.fail('token creation did fail'); 366 | }); 367 | }); 368 | 369 | it('should not allow tokens to be traded during bidding in phase 2', function() { 370 | return NEToken.deployed().then(net => { 371 | return net.transfer(accounts[1], 10, { 372 | from: accounts[0], 373 | gas: 2099999, 374 | gasPrice: 20000000000 375 | }); 376 | }).then(() => { 377 | assert.fail('trading did not fail'); 378 | }).catch(e => { 379 | if (e.name == 'Error') { 380 | assert.ok(true); 381 | } else { 382 | assert.fail('token creation did not fail'); 383 | } 384 | }); 385 | }); 386 | 387 | 388 | it('no finalization before the end when the cap conditions are not met', function() { 389 | let net = null; 390 | return NEToken.deployed().then(instance => { 391 | net = instance; 392 | return net.finalize({ 393 | from: ethFundDeposit, 394 | gas: 2099999, 395 | gasPrice: 20000000000 396 | }); 397 | }).then(() => { 398 | assert.fail('should not be possible to finalize'); 399 | }).catch(e => { 400 | if (e.name == 'Error') { 401 | assert.ok(true); 402 | } else { 403 | assert.fail('should not be possible to finalize'); 404 | } 405 | }); 406 | }); 407 | 408 | it('should not issue tokens after the funding has ended (because of reaching fundingEndBlock)', function() { 409 | let net = null; 410 | const weis = standardBid; 411 | return NEToken.deployed().then(instance => { 412 | net = instance; 413 | return blockNumber(); 414 | }).then(currentBlock => { 415 | // mine necessary amount of blocks 416 | return mineBlocks(fundingEndBlock - currentBlock); // we need to be > than fundingEndBlock 417 | }).then(() => { 418 | return net.createTokens({ 419 | from: accounts[2], 420 | value: weis, 421 | gas: 2099999, 422 | gasPrice: 20000000000 423 | }); 424 | }).then(balance => { 425 | assert.fail('token creation did not fail'); 426 | }).catch(e => { 427 | if (e.name == 'Error') { 428 | assert.ok(true); 429 | } else { 430 | assert.fail('token creation did not fail'); 431 | } 432 | }); 433 | }); 434 | 435 | it('should not allow tokens to be traded until contract is finalized', function() { 436 | return NEToken.deployed().then(net => { 437 | return net.transfer(accounts[1], 10, { 438 | from: accounts[0], 439 | gas: 2099999, 440 | gasPrice: 20000000000 441 | }); 442 | }).then(() => { 443 | assert.fail('trading did not fail'); 444 | }).catch(e => { 445 | if (e.name == 'Error') { 446 | assert.ok(true); 447 | } else { 448 | assert.fail('trading did not fail'); 449 | } 450 | }); 451 | }); 452 | 453 | it('only the owner can call the finalization', function() { 454 | let net = null; 455 | return NEToken.deployed().then(instance => { 456 | net = instance; 457 | return net.finalize({ 458 | from: accounts[0], 459 | gas: 2099999, 460 | gasPrice: 20000000000 461 | }); 462 | }).then(() => { 463 | assert.fail('finalization should not be allowed'); 464 | }).catch(e => { 465 | if (e.name == 'Error') { 466 | assert.ok(true); 467 | } else { 468 | assert.fail('finalization should not be allowed'); 469 | } 470 | }); 471 | }); 472 | 473 | it('only the owner can call pause', function() { 474 | let net = null; 475 | return NEToken.deployed().then(instance => { 476 | net = instance; 477 | return net.pause({ 478 | from: accounts[0], 479 | gas: 2099999, 480 | gasPrice: 20000000000 481 | }); 482 | }).then(() => { 483 | assert.fail('pausing should not be allowed'); 484 | }).catch(e => { 485 | if (e.name == 'Error') { 486 | assert.ok(true); 487 | } else { 488 | assert.fail('pausing should not be allowed'); 489 | } 490 | }); 491 | }); 492 | 493 | it('should allow finalization', function() { 494 | let net = null; 495 | const gasUsed = 34016; // calculated by running the transaction once 496 | const gasPrice = 20000000000; 497 | return NEToken.deployed().then(instance => { 498 | net = instance; 499 | return net.finalize({ 500 | from: ethFundDeposit, 501 | gas: 2099999, 502 | gasPrice: 20000000000 503 | }); 504 | }).then(() => { 505 | return net.state.call(); 506 | }).then(state => { 507 | assert.ok(convertInt(state) === isFinalized); 508 | return web3.eth.getBalance(ethFundDeposit); 509 | }).then(balance => { 510 | assert.ok(balance >= (2*standardBid+ethReceivedMin+initialFundBalance-(gasUsed*gasPrice)), 'balance is not correctly updated'); 511 | }).catch(() => { 512 | assert.fail('could not finalize contract'); 513 | }); 514 | }); 515 | 516 | it('can pause while finalized', function() { 517 | let net = null; 518 | return NEToken.deployed().then(instance => { 519 | net = instance; 520 | return net.pause({ 521 | from: ethFundDeposit, 522 | gas: 2099999, 523 | gasPrice: 20000000000 524 | }); 525 | }).then(() => { 526 | return net.state.call(); 527 | }).then(state => { 528 | assert.ok(convertInt(state) === isPaused, 'should be paused'); 529 | }) 530 | }); 531 | 532 | it('should not allow tokens to be traded while paused', function() { 533 | return NEToken.deployed().then(net => { 534 | return net.transfer(accounts[1], 10, { 535 | from: accounts[0], 536 | gas: 2099999, 537 | gasPrice: 20000000000 538 | }); 539 | }).then(() => { 540 | assert.fail('trading did not fail'); 541 | }).catch(e => { 542 | if (e.name == 'Error') { 543 | assert.ok(true); 544 | } else { 545 | assert.fail('trading did not fail'); 546 | } 547 | }); 548 | }); 549 | 550 | it('only the owner can call proceed', function() { 551 | let net = null; 552 | return NEToken.deployed().then(instance => { 553 | net = instance; 554 | return net.proceed({ 555 | from: accounts[0], 556 | gas: 2099999, 557 | gasPrice: 20000000000 558 | }); 559 | }).then(() => { 560 | assert.fail('proceeding should not be allowed'); 561 | }).catch(e => { 562 | if (e.name == 'Error') { 563 | assert.ok(true); 564 | } else { 565 | assert.fail('proceeding should not be allowed'); 566 | } 567 | }); 568 | }); 569 | 570 | it('can proceed to finalized', function() { 571 | let net = null; 572 | return NEToken.deployed().then(instance => { 573 | net = instance; 574 | return net.proceed({ 575 | from: ethFundDeposit, 576 | gas: 2099999, 577 | gasPrice: 20000000000 578 | }); 579 | }).then(() => { 580 | return net.state.call(); 581 | }).then(state => { 582 | assert.ok(convertInt(state) === isFinalized, 'should be proceeded'); 583 | }) 584 | }); 585 | 586 | it('should allow tokens to be traded after finalization', function() { 587 | return NEToken.deployed().then(net => { 588 | return net.transfer(accounts[3], 10, { 589 | from: accounts[0], 590 | gas: 2099999, 591 | gasPrice: 20000000000 592 | }); 593 | }).then(() => { 594 | assert.ok(true); 595 | }).catch(() => { 596 | assert.fail('could not trade tokens'); 597 | }); 598 | }); 599 | 600 | it('no finalization after it has been called before', function() { 601 | let net = null; 602 | return NEToken.deployed().then(instance => { 603 | net = instance; 604 | return net.finalize({ 605 | from: ethFundDeposit, 606 | gas: 2099999, 607 | gasPrice: 20000000000 608 | }); 609 | }).then(() => { 610 | assert.fail('should not be possible to finalize'); 611 | }).catch(e => { 612 | if (e.name == 'Error') { 613 | assert.ok(true); 614 | } else { 615 | assert.fail('should not be possible to finalize'); 616 | } 617 | }); 618 | }); 619 | 620 | it('only the owner can start the redeeming period', function() { 621 | let net = null; 622 | return NEToken.deployed().then(instance => { 623 | net = instance; 624 | return net.startRedeeming({ 625 | from: accounts[0], 626 | gas: 2099999, 627 | gasPrice: 20000000000 628 | }); 629 | }).then(() => { 630 | assert.fail('redeeming should not be allowed'); 631 | }).catch(e => { 632 | if (e.name == 'Error') { 633 | assert.ok(true); 634 | } else { 635 | assert.fail('redeeming should not be allowed'); 636 | } 637 | }); 638 | }); 639 | 640 | it('should allow to start the redeeming period', function() { 641 | let net = null; 642 | return NEToken.deployed().then(instance => { 643 | net = instance; 644 | return net.startRedeeming({ 645 | from: ethFundDeposit, 646 | gas: 2099999, 647 | gasPrice: 20000000000 648 | }); 649 | }).then(() => { 650 | return net.state.call(); 651 | }).then(state => { 652 | assert.ok(convertInt(state) === isRedeeming); 653 | }).catch(() => { 654 | assert.fail('could not start redeeming period'); 655 | }); 656 | }); 657 | 658 | it('can pause while redeeming', function() { 659 | let net = null; 660 | return NEToken.deployed().then(instance => { 661 | net = instance; 662 | return net.pause({ 663 | from: ethFundDeposit, 664 | gas: 2099999, 665 | gasPrice: 20000000000 666 | }); 667 | }).then(() => { 668 | return net.state.call(); 669 | }).then(state => { 670 | assert.ok(convertInt(state) === isPaused, 'should be paused'); 671 | }) 672 | }); 673 | 674 | it('should not allow redeeming while paused', function() { 675 | return NEToken.deployed().then(net => { 676 | return net.redeemTokens("0x3D7D9AF2BF88E91A9D73D10F79C278424DDC89D83D7D9AF2BF88E91A9D73D45A", { 677 | from: accounts[0], 678 | gas: 2099999, 679 | gasPrice: 20000000000 680 | }); 681 | }).then(() => { 682 | assert.fail('redeeming did not fail'); 683 | }).catch(e => { 684 | if (e.name == 'Error') { 685 | assert.ok(true); 686 | } else { 687 | assert.fail('redeeming did not fail'); 688 | } 689 | }); 690 | }); 691 | 692 | it('can proceed to redeeming', function() { 693 | let net = null; 694 | return NEToken.deployed().then(instance => { 695 | net = instance; 696 | return net.proceed({ 697 | from: ethFundDeposit, 698 | gas: 2099999, 699 | gasPrice: 20000000000 700 | }); 701 | }).then(() => { 702 | return net.state.call(); 703 | }).then(state => { 704 | assert.ok(convertInt(state) === isRedeeming, 'should be proceeded'); 705 | }) 706 | }); 707 | 708 | 709 | it('should not allow to redeem tokens below the token minimum', function() { 710 | return NEToken.deployed().then(net => { 711 | return net.redeemTokens("0x3D7D9AF2BF88E91A9D73D10F79C278424DDC89D8", { 712 | from: accounts[3], 713 | gas: 2099999, 714 | gasPrice: 20000000000 715 | }); 716 | }).then(() => { 717 | assert.fail('redeeming did not fail'); 718 | }).catch(e => { 719 | if (e.name == 'Error') { 720 | assert.ok(true); 721 | } else { 722 | assert.fail('redeeming did not fail'); 723 | } 724 | }); 725 | }); 726 | 727 | 728 | it('should allow redeeming and transfer the tokens to us', function() { 729 | let net = null; 730 | return NEToken.deployed().then(_net => { 731 | net = _net; 732 | return net.redeemTokens("0x3D7D9AF2BF88E91A9D73D10F79C278424DDC89D83D7D9AF2BF88E91A9D73D45A", { 733 | from: accounts[0], 734 | gas: 2099999, 735 | gasPrice: 20000000000 736 | }); 737 | }).then(ret => { 738 | assert.ok(getEvent('LogRedeemNET', ret), 'should log a RedeemNET event'); 739 | return net.balanceOf.call(accounts[0]); 740 | }).then(balance => { 741 | assert.equal(balance, 0, 'there is still some balance left after redeeming'); 742 | return net.balanceOf.call(ethFundDeposit); 743 | }).then(fundBalance => { 744 | assert.equal(fundBalance, standardBid * tokenFirstExchangeRate, 'the tokens were not transfered to our balance'); 745 | }).catch(() => { 746 | assert.fail('could not redeem'); 747 | }); 748 | }); 749 | }); 750 | --------------------------------------------------------------------------------