├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── audits ├── fantom-audit-first.pdf ├── fantom-audit-second.pdf └── testsuite │ ├── fantom-audit-truffle-one │ ├── README.md │ ├── contracts │ │ └── FantomToken.sol │ ├── largeTestAccounts.sh │ ├── migrations │ │ └── empty_file │ ├── package-lock.json │ ├── package.json │ ├── test │ │ ├── tests │ │ │ ├── erc20.js │ │ │ ├── fantom.js │ │ │ ├── gas-consumption.js │ │ │ ├── token-math.js │ │ │ └── transferMultiple.js │ │ └── utils │ │ │ ├── deployer.js │ │ │ └── testHelpers.js │ └── truffle.js │ └── fantom-audit-truffle-two │ ├── README.md │ ├── contracts │ └── FantomToken.sol │ ├── largeTestAccounts.sh │ ├── migrations │ └── empty_file │ ├── package-lock.json │ ├── package.json │ ├── test │ ├── tests │ │ ├── erc20.js │ │ ├── fantom.js │ │ ├── gas-consumption.js │ │ ├── token-math.js │ │ ├── tokensale.js │ │ ├── transferMultiple.js │ │ └── transferMultipleExtended.js │ └── utils │ │ ├── deployer.js │ │ └── testHelpers.js │ └── truffle.js ├── lottery ├── README.md ├── lottery_1_input.txt ├── lottery_1_result.txt ├── lottery_1_result_canonical.txt ├── lottery_2_input.txt ├── lottery_2_input_raw.txt ├── lottery_2_result.txt ├── lottery_2_result_canonical.txt └── whitelist.txt └── sol ├── FantomToken.sol └── FantomTokenTest.sol /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantom-foundation/tokensale/eb260602c431f6a522bbfc39100dbef9ac8a320a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /sol/FantomToken_preSimplification.sol 2 | /sol/FantomToken/* 3 | notes.txt 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fantom Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fantom Foundation FTM token public sale contract 2 | 3 | For details, please visit: http://fantom.foundation 4 | 5 | Some test results can be seen at https://github.com/alex-kampa/test_FantomToken -------------------------------------------------------------------------------- /audits/fantom-audit-first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantom-foundation/tokensale/eb260602c431f6a522bbfc39100dbef9ac8a320a/audits/fantom-audit-first.pdf -------------------------------------------------------------------------------- /audits/fantom-audit-second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantom-foundation/tokensale/eb260602c431f6a522bbfc39100dbef9ac8a320a/audits/fantom-audit-second.pdf -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/README.md: -------------------------------------------------------------------------------- 1 | # Fantom Token Review 2 | 3 | This directory contains the testing suite used in the security review. In order 4 | to run the tests the requisite node modules need to be installed. This can be 5 | achieved via: 6 | 7 | ``` 8 | $ npm install 9 | ``` 10 | 11 | A ganache-like client, with large amount of ether (`testrpc` and `geth --dev` are alternative options) need to be running, i.e 12 | 13 | ``` 14 | $ ./largeTestAccounts.sh 15 | ``` 16 | 17 | The tests can then be executed via 18 | 19 | ``` 20 | truffle test 21 | ``` 22 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/contracts/FantomToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // 5 | // Fantom Foundation FTM token public sale contract 6 | // 7 | // For details, please visit: http://fantom.foundation 8 | // 9 | // 10 | // written by Alex Kampa - ak@sikoba.com 11 | // 12 | // ---------------------------------------------------------------------------- 13 | 14 | 15 | // ---------------------------------------------------------------------------- 16 | // 17 | // SafeMath 18 | // 19 | // ---------------------------------------------------------------------------- 20 | 21 | library SafeMath { 22 | 23 | function add(uint a, uint b) internal pure returns (uint c) { 24 | c = a + b; 25 | require(c >= a); 26 | } 27 | 28 | function sub(uint a, uint b) internal pure returns (uint c) { 29 | require(b <= a); 30 | c = a - b; 31 | } 32 | 33 | function mul(uint a, uint b) internal pure returns (uint c) { 34 | c = a * b; 35 | require(a == 0 || c / a == b); 36 | } 37 | 38 | } 39 | 40 | 41 | // ---------------------------------------------------------------------------- 42 | // 43 | // Utils 44 | // 45 | // ---------------------------------------------------------------------------- 46 | 47 | contract Utils { 48 | 49 | function atNow() public view returns (uint) { 50 | return block.timestamp; 51 | } 52 | 53 | } 54 | 55 | 56 | // ---------------------------------------------------------------------------- 57 | // 58 | // Owned 59 | // 60 | // ---------------------------------------------------------------------------- 61 | 62 | contract Owned { 63 | 64 | address public owner; 65 | address public newOwner; 66 | 67 | mapping(address => bool) public isAdmin; 68 | 69 | event OwnershipTransferProposed(address indexed _from, address indexed _to); 70 | event OwnershipTransferred(address indexed _from, address indexed _to); 71 | event AdminChange(address indexed _admin, bool _status); 72 | 73 | modifier onlyOwner {require(msg.sender == owner); _;} 74 | modifier onlyAdmin {require(isAdmin[msg.sender]); _;} 75 | 76 | constructor() public { 77 | owner = msg.sender; 78 | isAdmin[owner] = true; 79 | } 80 | 81 | function transferOwnership(address _newOwner) public onlyOwner { 82 | require(_newOwner != address(0x0)); 83 | emit OwnershipTransferProposed(owner, _newOwner); 84 | newOwner = _newOwner; 85 | } 86 | 87 | function acceptOwnership() public { 88 | require(msg.sender == newOwner); 89 | emit OwnershipTransferred(owner, newOwner); 90 | owner = newOwner; 91 | } 92 | 93 | function addAdmin(address _a) public onlyOwner { 94 | require(isAdmin[_a] == false); 95 | isAdmin[_a] = true; 96 | emit AdminChange(_a, true); 97 | } 98 | 99 | function removeAdmin(address _a) public onlyOwner { 100 | require(isAdmin[_a] == true); 101 | isAdmin[_a] = false; 102 | emit AdminChange(_a, false); 103 | } 104 | 105 | } 106 | 107 | 108 | // ---------------------------------------------------------------------------- 109 | // 110 | // Wallet 111 | // 112 | // ---------------------------------------------------------------------------- 113 | 114 | contract Wallet is Owned { 115 | 116 | address public wallet; 117 | 118 | event WalletUpdated(address newWallet); 119 | 120 | constructor() public { 121 | wallet = owner; 122 | } 123 | 124 | function setWallet(address _wallet) public onlyOwner { 125 | require(_wallet != address(0x0)); 126 | wallet = _wallet; 127 | emit WalletUpdated(_wallet); 128 | } 129 | 130 | } 131 | 132 | 133 | // ---------------------------------------------------------------------------- 134 | // 135 | // ERC20Interface 136 | // 137 | // ---------------------------------------------------------------------------- 138 | 139 | contract ERC20Interface { 140 | 141 | event Transfer(address indexed _from, address indexed _to, uint _value); 142 | event Approval(address indexed _owner, address indexed _spender, uint _value); 143 | 144 | function totalSupply() public view returns (uint); 145 | function balanceOf(address _owner) public view returns (uint balance); 146 | function transfer(address _to, uint _value) public returns (bool success); 147 | function transferFrom(address _from, address _to, uint _value) public returns (bool success); 148 | function approve(address _spender, uint _value) public returns (bool success); 149 | function allowance(address _owner, address _spender) public view returns (uint remaining); 150 | 151 | } 152 | 153 | 154 | // ---------------------------------------------------------------------------- 155 | // 156 | // ERC Token Standard #20 157 | // 158 | // ---------------------------------------------------------------------------- 159 | 160 | contract ERC20Token is ERC20Interface, Owned { 161 | 162 | using SafeMath for uint; 163 | 164 | uint public tokensIssuedTotal = 0; 165 | mapping(address => uint) balances; 166 | mapping(address => mapping (address => uint)) allowed; 167 | 168 | function totalSupply() public view returns (uint) { 169 | return tokensIssuedTotal; 170 | } 171 | 172 | function balanceOf(address _owner) public view returns (uint balance) { 173 | return balances[_owner]; 174 | } 175 | 176 | function transfer(address _to, uint _amount) public returns (bool success) { 177 | require(balances[msg.sender] >= _amount); 178 | balances[msg.sender] = balances[msg.sender].sub(_amount); 179 | balances[_to] = balances[_to].add(_amount); 180 | emit Transfer(msg.sender, _to, _amount); 181 | return true; 182 | } 183 | 184 | function approve(address _spender, uint _amount) public returns (bool success) { 185 | allowed[msg.sender][_spender] = _amount; 186 | emit Approval(msg.sender, _spender, _amount); 187 | return true; 188 | } 189 | 190 | function transferFrom(address _from, address _to, uint _amount) public returns (bool success) { 191 | require(balances[_from] >= _amount); 192 | require(allowed[_from][msg.sender] >= _amount); 193 | balances[_from] = balances[_from].sub(_amount); 194 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount); 195 | balances[_to] = balances[_to].add(_amount); 196 | emit Transfer(_from, _to, _amount); 197 | return true; 198 | } 199 | 200 | function allowance(address _owner, address _spender) public view returns (uint remaining) { 201 | return allowed[_owner][_spender]; 202 | } 203 | 204 | } 205 | 206 | 207 | // ---------------------------------------------------------------------------- 208 | // 209 | // LockSlots 210 | // 211 | // ---------------------------------------------------------------------------- 212 | 213 | contract LockSlots is ERC20Token, Utils { 214 | 215 | using SafeMath for uint; 216 | 217 | uint8 public constant LOCK_SLOTS = 5; 218 | mapping(address => uint[LOCK_SLOTS]) public lockTerm; 219 | mapping(address => uint[LOCK_SLOTS]) public lockAmnt; 220 | mapping(address => bool) public mayHaveLockedTokens; 221 | 222 | event RegisteredLockedTokens(address indexed account, uint indexed idx, uint tokens, uint term); 223 | 224 | function registerLockedTokens(address _account, uint _tokens, uint _term) internal returns (uint idx) { 225 | require(_term > atNow(), "lock term must be in the future"); 226 | 227 | // find a slot (clean up while doing this) 228 | // use either the existing slot with the exact same term, 229 | // of which there can be at most one, or the first empty slot 230 | idx = 9999; 231 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 232 | uint[LOCK_SLOTS] storage amnt = lockAmnt[_account]; 233 | for (uint i = 0; i < LOCK_SLOTS; i++) { 234 | if (term[i] < atNow()) { 235 | term[i] = 0; 236 | amnt[i] = 0; 237 | if (idx == 9999) idx = i; 238 | } 239 | if (term[i] == _term) idx = i; 240 | } 241 | 242 | // fail if no slot was found 243 | require(idx != 9999, "registerLockedTokens: no available slot found"); 244 | 245 | // register locked tokens 246 | if (term[idx] == 0) term[idx] = _term; 247 | amnt[idx] = amnt[idx].add(_tokens); 248 | mayHaveLockedTokens[_account] = true; 249 | emit RegisteredLockedTokens(_account, idx, _tokens, _term); 250 | } 251 | 252 | // public view functions 253 | 254 | function lockedTokens(address _account) public view returns (uint) { 255 | if (!mayHaveLockedTokens[_account]) return 0; 256 | return pNumberOfLockedTokens(_account); 257 | } 258 | 259 | function unlockedTokens(address _account) public view returns (uint) { 260 | return balances[_account].sub(lockedTokens(_account)); 261 | } 262 | 263 | function isAvailableLockSlot(address _account, uint _term) public view returns (bool) { 264 | if (!mayHaveLockedTokens[_account]) return true; 265 | if (_term < atNow()) return true; 266 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 267 | for (uint i = 0; i < LOCK_SLOTS; i++) { 268 | if (term[i] < atNow() || term[i] == _term) return true; 269 | } 270 | return false; 271 | } 272 | 273 | // internal and private functions 274 | 275 | function unlockedTokensInternal(address _account) internal returns (uint) { 276 | // updates mayHaveLockedTokens if necessary 277 | if (!mayHaveLockedTokens[_account]) return balances[_account]; 278 | uint locked = pNumberOfLockedTokens(_account); 279 | if (locked == 0) mayHaveLockedTokens[_account] = false; 280 | return balances[_account].sub(locked); 281 | } 282 | 283 | function pNumberOfLockedTokens(address _account) private view returns (uint locked) { 284 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 285 | uint[LOCK_SLOTS] storage amnt = lockAmnt[_account]; 286 | for (uint i = 0; i < LOCK_SLOTS; i++) { 287 | if (term[i] >= atNow()) locked = locked.add(amnt[i]); 288 | } 289 | } 290 | 291 | } 292 | 293 | 294 | // ---------------------------------------------------------------------------- 295 | // 296 | // FantomIcoDates 297 | // 298 | // ---------------------------------------------------------------------------- 299 | 300 | contract FantomIcoDates is Owned, Utils { 301 | 302 | uint public dateMainStart;// = 1527861600; // 01-JUN-2018 14:00 UTC 303 | uint public dateMainEnd; // = 1527861600 + 15 days; 304 | 305 | uint public DATE_LIMIT;// = 1527861600 + 180 days; 306 | 307 | event IcoDateUpdated(uint id, uint unixts); 308 | 309 | constructor(uint _dateMainStart, uint _dateMainEnd) public { 310 | dateMainStart = _dateMainStart; 311 | dateMainEnd = _dateMainEnd; 312 | DATE_LIMIT = dateMainEnd + 180 days; 313 | require(atNow() < dateMainStart); 314 | checkDateOrder(); 315 | } 316 | 317 | // check dates 318 | 319 | function checkDateOrder() internal view { 320 | require(dateMainStart < dateMainEnd); 321 | require(dateMainEnd < DATE_LIMIT); 322 | } 323 | 324 | // set ico dates 325 | 326 | function setDateMainStart(uint _unixts) public onlyOwner { 327 | require(atNow() < _unixts && atNow() < dateMainStart); 328 | dateMainStart = _unixts; 329 | checkDateOrder(); 330 | emit IcoDateUpdated(1, _unixts); 331 | } 332 | 333 | function setDateMainEnd(uint _unixts) public onlyOwner { 334 | require(atNow() < _unixts && atNow() < dateMainEnd); 335 | dateMainEnd = _unixts; 336 | checkDateOrder(); 337 | emit IcoDateUpdated(1, _unixts); 338 | } 339 | 340 | // where are we? 341 | 342 | function isMainFirstDay() public view returns (bool) { 343 | if (atNow() > dateMainStart && atNow() <= dateMainStart + 1 days) return true; 344 | return false; 345 | } 346 | 347 | function isMain() public view returns (bool) { 348 | if (atNow() > dateMainStart && atNow() < dateMainEnd) return true; 349 | return false; 350 | } 351 | 352 | } 353 | 354 | // ---------------------------------------------------------------------------- 355 | // 356 | // Fantom public token sale 357 | // 358 | // ---------------------------------------------------------------------------- 359 | 360 | contract FantomToken is ERC20Token, Wallet, LockSlots, FantomIcoDates { 361 | 362 | // Utility variable 363 | 364 | uint constant E18 = 10**18; 365 | 366 | // Basic token data 367 | 368 | string public constant name = "Fantom Token"; 369 | string public constant symbol = "FTM"; 370 | uint public constant decimals = 18; 371 | 372 | // crowdsale parameters 373 | 374 | uint public tokensPerEth = 10000; 375 | 376 | uint public constant MINIMUM_CONTRIBUTION = 0.5 ether; 377 | 378 | uint public constant TOKEN_TOTAL_SUPPLY = 1000000000 * E18; 379 | uint public constant TOKEN_MAIN_CAP = 600000000 * E18; 380 | 381 | bool public tokensTradeable; 382 | 383 | // whitelisting 384 | 385 | mapping(address => bool) public whitelist; 386 | uint public numberWhitelisted; 387 | 388 | // track main sale 389 | 390 | uint public tokensMain; 391 | mapping(address => uint) public balancesMain; 392 | 393 | uint public totalEthContributed; 394 | mapping(address => uint) public ethContributed; 395 | 396 | // tracking tokens minted 397 | 398 | uint public tokensMinted; 399 | mapping(address => uint) public balancesMinted; 400 | mapping(address => mapping(uint => uint)) public balancesMintedByType; 401 | 402 | // migration variable 403 | 404 | bool public isMigrationPhaseOpen; 405 | 406 | // Events --------------------------------------------- 407 | 408 | event UpdatedTokensPerEth(uint tokensPerEth); 409 | event Whitelisted(address indexed account, uint countWhitelisted); 410 | event TokensMinted(uint indexed mintType, address indexed account, uint tokens, uint term); 411 | event RegisterContribution(address indexed account, uint tokensIssued, uint ethContributed, uint ethReturned); 412 | event TokenExchangeRequested(address indexed account, uint tokens); 413 | 414 | // Basic Functions ------------------------------------ 415 | 416 | constructor(uint dateMainStart, uint dateMainEnd) 417 | FantomIcoDates(dateMainStart, dateMainEnd) 418 | public {} 419 | 420 | function () public payable { 421 | buyTokens(); 422 | } 423 | 424 | // Information functions 425 | 426 | function availableToMint() public view returns (uint) { 427 | return TOKEN_TOTAL_SUPPLY.sub(TOKEN_MAIN_CAP).sub(tokensMinted); 428 | } 429 | 430 | function firstDayTokenLimit() public view returns (uint) { 431 | if (numberWhitelisted == 0) return 0; 432 | return TOKEN_MAIN_CAP / numberWhitelisted; 433 | } 434 | 435 | function ethToTokens(uint _eth) public view returns (uint tokens) { 436 | tokens = _eth.mul(tokensPerEth); 437 | } 438 | 439 | function tokensToEth(uint _tokens) public view returns (uint eth) { 440 | eth = _tokens / tokensPerEth; 441 | } 442 | 443 | // Admin functions 444 | 445 | function addToWhitelist(address _account) public onlyAdmin { 446 | pWhitelist(_account); 447 | } 448 | 449 | function addToWhitelistMultiple(address[] _addresses) public onlyAdmin { 450 | for (uint i = 0; i < _addresses.length; i++) { 451 | pWhitelist(_addresses[i]); 452 | } 453 | } 454 | 455 | function pWhitelist(address _account) internal { 456 | require(!isMainFirstDay()); 457 | if (whitelist[_account]) return; 458 | whitelist[_account] = true; 459 | numberWhitelisted = numberWhitelisted.add(1); 460 | emit Whitelisted(_account, numberWhitelisted); 461 | } 462 | 463 | // Owner functions ------------------------------------ 464 | 465 | function updateTokensPerEth(uint _tokens_per_eth) public onlyOwner { 466 | require(atNow() < dateMainStart); 467 | tokensPerEth = _tokens_per_eth; 468 | emit UpdatedTokensPerEth(tokensPerEth); 469 | } 470 | 471 | function makeTradeable() public onlyOwner { 472 | require(atNow() > dateMainEnd); 473 | tokensTradeable = true; 474 | } 475 | 476 | function openMigrationPhase() public onlyOwner { 477 | require(atNow() > dateMainEnd); 478 | isMigrationPhaseOpen = true; 479 | } 480 | 481 | // Token minting -------------------------------------- 482 | 483 | function mintTokens(uint _mint_type, address _account, uint _tokens) public onlyOwner { 484 | pMintTokens(_mint_type, _account, _tokens, 0); 485 | } 486 | 487 | function mintTokensMultiple(uint _mint_type, address[] _accounts, uint[] _tokens) public onlyOwner { 488 | require(_accounts.length == _tokens.length); 489 | for (uint i = 0; i < _accounts.length; i++) { 490 | pMintTokens(_mint_type, _accounts[i], _tokens[i], 0); 491 | } 492 | } 493 | 494 | function mintTokensLocked(uint _mint_type, address _account, uint _tokens, uint _term) public onlyOwner { 495 | pMintTokens(_mint_type, _account, _tokens, _term); 496 | } 497 | 498 | function mintTokensLockedMultiple(uint _mint_type, address[] _accounts, uint[] _tokens, uint[] _terms) public onlyOwner { 499 | require(_accounts.length == _tokens.length); 500 | require(_accounts.length == _terms.length); 501 | for (uint i = 0; i < _accounts.length; i++) { 502 | pMintTokens(_mint_type, _accounts[i], _tokens[i], _terms[i]); 503 | } 504 | } 505 | 506 | function pMintTokens(uint _mint_type, address _account, uint _tokens, uint _term) private { 507 | require(whitelist[_account]); 508 | require(_account != 0x0); 509 | require(_tokens > 0); 510 | require(_tokens <= availableToMint(), "not enough tokens available to mint"); 511 | require(_term == 0 || _term > atNow(), "either without lock term, or lock term must be in the future"); 512 | 513 | // register locked tokens (will throw if no slot is found) 514 | if (_term > 0) registerLockedTokens(_account, _tokens, _term); 515 | 516 | // update 517 | balances[_account] = balances[_account].add(_tokens); 518 | balancesMinted[_account] = balancesMinted[_account].add(_tokens); 519 | balancesMintedByType[_account][_mint_type] = balancesMintedByType[_account][_mint_type].add(_tokens); 520 | tokensMinted = tokensMinted.add(_tokens); 521 | tokensIssuedTotal = tokensIssuedTotal.add(_tokens); 522 | 523 | // log event 524 | emit Transfer(0x0, _account, _tokens); 525 | emit TokensMinted(_mint_type, _account, _tokens, _term); 526 | } 527 | 528 | // Main sale ------------------------------------------ 529 | 530 | function buyTokens() private { 531 | 532 | require(isMain()); 533 | require(msg.value >= MINIMUM_CONTRIBUTION); 534 | require(whitelist[msg.sender]); 535 | 536 | uint tokens_available; 537 | 538 | if (isMainFirstDay()) { 539 | tokens_available = firstDayTokenLimit().sub(balancesMain[msg.sender]); 540 | } else if (isMain()) { 541 | tokens_available = TOKEN_MAIN_CAP.sub(tokensMain); 542 | } 543 | 544 | require (tokens_available > 0); 545 | 546 | uint tokens_requested = ethToTokens(msg.value); 547 | uint tokens_issued = tokens_requested; 548 | 549 | uint eth_contributed = msg.value; 550 | uint eth_returned; 551 | 552 | if (tokens_requested > tokens_available) { 553 | tokens_issued = tokens_available; 554 | eth_returned = tokensToEth(tokens_requested.sub(tokens_available)); 555 | eth_contributed = msg.value.sub(eth_returned); 556 | } 557 | 558 | balances[msg.sender] = balances[msg.sender].add(tokens_issued); 559 | balancesMain[msg.sender] = balancesMain[msg.sender].add(tokens_issued); 560 | tokensMain = tokensMain.add(tokens_issued); 561 | tokensIssuedTotal = tokensIssuedTotal.add(tokens_issued); 562 | 563 | ethContributed[msg.sender] = ethContributed[msg.sender].add(eth_contributed); 564 | totalEthContributed = totalEthContributed.add(eth_contributed); 565 | 566 | // ether transfers 567 | if (eth_returned > 0) msg.sender.transfer(eth_returned); 568 | wallet.transfer(eth_contributed); 569 | 570 | // log 571 | emit Transfer(0x0, msg.sender, tokens_issued); 572 | emit RegisterContribution(msg.sender, tokens_issued, eth_contributed, eth_returned); 573 | } 574 | 575 | // Token exchange / migration to new platform --------- 576 | 577 | function requestTokenExchangeMax() public { 578 | requestTokenExchange(unlockedTokensInternal(msg.sender)); 579 | } 580 | 581 | function requestTokenExchange(uint _tokens) public { 582 | require(isMigrationPhaseOpen); 583 | require(_tokens > 0 && _tokens <= unlockedTokensInternal(msg.sender)); 584 | balances[msg.sender] = balances[msg.sender].sub(_tokens); 585 | tokensIssuedTotal = tokensIssuedTotal.sub(_tokens); 586 | emit Transfer(msg.sender, 0x0, _tokens); 587 | emit TokenExchangeRequested(msg.sender, _tokens); 588 | } 589 | 590 | // ERC20 functions ------------------- 591 | 592 | /* Transfer out any accidentally sent ERC20 tokens */ 593 | 594 | function transferAnyERC20Token(address _token_address, uint _amount) public onlyOwner returns (bool success) { 595 | return ERC20Interface(_token_address).transfer(owner, _amount); 596 | } 597 | 598 | /* Override "transfer" */ 599 | 600 | function transfer(address _to, uint _amount) public returns (bool success) { 601 | require(tokensTradeable); 602 | require(_amount <= unlockedTokensInternal(msg.sender)); 603 | return super.transfer(_to, _amount); 604 | } 605 | 606 | /* Override "transferFrom" */ 607 | 608 | function transferFrom(address _from, address _to, uint _amount) public returns (bool success) { 609 | require(tokensTradeable); 610 | require(_amount <= unlockedTokensInternal(_from)); 611 | return super.transferFrom(_from, _to, _amount); 612 | } 613 | 614 | /* Multiple token transfers from one address to save gas */ 615 | 616 | function transferMultiple(address[] _addresses, uint[] _amounts) external { 617 | require(tokensTradeable); 618 | require(_addresses.length <= 100); 619 | require(_addresses.length == _amounts.length); 620 | 621 | // check token amounts 622 | uint tokens_to_transfer = 0; 623 | for (uint i = 0; i < _addresses.length; i++) { 624 | tokens_to_transfer = tokens_to_transfer.add(_amounts[i]); 625 | } 626 | require(tokens_to_transfer <= unlockedTokensInternal(msg.sender)); 627 | 628 | // do the transfers 629 | for (i = 0; i < _addresses.length; i++) { 630 | super.transfer(_addresses[i], _amounts[i]); 631 | } 632 | } 633 | 634 | } 635 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/largeTestAccounts.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if [ -z $(which ganache-cli) ] 4 | then 5 | BINARY=testrpc 6 | else 7 | BINARY=ganache-cli 8 | fi 9 | 10 | $BINARY \ 11 | --account="0xa0d312360cf7078785bcc243ae84d29b8578a4132a03dee6506be756c23783f5,100000000000000000000000000000000000000" \ 12 | --account="0x1b569f65c0550274b36156b30ce6f18fcf397e6a018fbe970ab84060bfc5f3b1,100000000000000000000000000000000000000" \ 13 | --account="0xe1cccd2387e787bd8f1ab6e7d480961f82afc9708f942b47d0f236267d9720ab,100000000000000000000000000000000000000" \ 14 | --account="0x4f445dad230412011c1821af71e5d211b74c67eac92c910bc9f92faafe4b6c32,100000000000000000000000000000000000000" \ 15 | --account="0x48e508caee9137ad6babb94f2ee3aaef327b6ad38928110b55fded8e46c1abff,100000000000000000000000000000000000000"\ 16 | --account="0x97338b16a5e47954252202169b92b6ffb3cc17d4254d4eb6724bf945ebfb55a3,100000000000000000000000000000000000000"\ 17 | --account="0xdf2daacafa1fae8e2984a5c02c4289c92fa71b0f7f525b5f90fc66ad9978849e,100000000000000000000000000000000000000"\ 18 | --gasLimit 10000000 19 | 20 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/migrations/empty_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantom-foundation/tokensale/eb260602c431f6a522bbfc39100dbef9ac8a320a/audits/testsuite/fantom-audit-truffle-one/migrations/empty_file -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "5.5.2", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 10 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 11 | "requires": { 12 | "co": "^4.6.0", 13 | "fast-deep-equal": "^1.0.0", 14 | "fast-json-stable-stringify": "^2.0.0", 15 | "json-schema-traverse": "^0.3.0" 16 | } 17 | }, 18 | "ansi-regex": { 19 | "version": "2.1.1", 20 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 21 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 22 | }, 23 | "async": { 24 | "version": "1.5.2", 25 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 26 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 27 | }, 28 | "balanced-match": { 29 | "version": "1.0.0", 30 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 31 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 32 | }, 33 | "bignumber.js": { 34 | "version": "5.0.0", 35 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", 36 | "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==" 37 | }, 38 | "bn.js": { 39 | "version": "4.11.6", 40 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", 41 | "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" 42 | }, 43 | "brace-expansion": { 44 | "version": "1.1.11", 45 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 46 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 47 | "requires": { 48 | "balanced-match": "^1.0.0", 49 | "concat-map": "0.0.1" 50 | } 51 | }, 52 | "builtin-modules": { 53 | "version": "1.1.1", 54 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 55 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" 56 | }, 57 | "camelcase": { 58 | "version": "3.0.0", 59 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", 60 | "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" 61 | }, 62 | "cliui": { 63 | "version": "3.2.0", 64 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", 65 | "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", 66 | "requires": { 67 | "string-width": "^1.0.1", 68 | "strip-ansi": "^3.0.1", 69 | "wrap-ansi": "^2.0.0" 70 | } 71 | }, 72 | "co": { 73 | "version": "4.6.0", 74 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 75 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 76 | }, 77 | "code-point-at": { 78 | "version": "1.1.0", 79 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 80 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 81 | }, 82 | "concat-map": { 83 | "version": "0.0.1", 84 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 85 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 86 | }, 87 | "crypto-js": { 88 | "version": "3.1.9-1", 89 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", 90 | "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" 91 | }, 92 | "debug": { 93 | "version": "3.1.0", 94 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 95 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 96 | "requires": { 97 | "ms": "2.0.0" 98 | } 99 | }, 100 | "decamelize": { 101 | "version": "1.2.0", 102 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 103 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 104 | }, 105 | "error-ex": { 106 | "version": "1.3.1", 107 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", 108 | "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", 109 | "requires": { 110 | "is-arrayish": "^0.2.1" 111 | } 112 | }, 113 | "ethjs-abi": { 114 | "version": "0.1.8", 115 | "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.1.8.tgz", 116 | "integrity": "sha1-zSiFg+1ijN+tr4re+juh28vKbBg=", 117 | "requires": { 118 | "bn.js": "4.11.6", 119 | "js-sha3": "0.5.5", 120 | "number-to-bn": "1.7.0" 121 | } 122 | }, 123 | "fast-deep-equal": { 124 | "version": "1.1.0", 125 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", 126 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" 127 | }, 128 | "fast-json-stable-stringify": { 129 | "version": "2.0.0", 130 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 131 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 132 | }, 133 | "find-up": { 134 | "version": "1.1.2", 135 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", 136 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", 137 | "requires": { 138 | "path-exists": "^2.0.0", 139 | "pinkie-promise": "^2.0.0" 140 | } 141 | }, 142 | "fs-extra": { 143 | "version": "0.30.0", 144 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", 145 | "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", 146 | "requires": { 147 | "graceful-fs": "^4.1.2", 148 | "jsonfile": "^2.1.0", 149 | "klaw": "^1.0.0", 150 | "path-is-absolute": "^1.0.0", 151 | "rimraf": "^2.2.8" 152 | } 153 | }, 154 | "fs.realpath": { 155 | "version": "1.0.0", 156 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 157 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 158 | }, 159 | "get-caller-file": { 160 | "version": "1.0.2", 161 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", 162 | "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" 163 | }, 164 | "glob": { 165 | "version": "7.1.2", 166 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 167 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 168 | "requires": { 169 | "fs.realpath": "^1.0.0", 170 | "inflight": "^1.0.4", 171 | "inherits": "2", 172 | "minimatch": "^3.0.4", 173 | "once": "^1.3.0", 174 | "path-is-absolute": "^1.0.0" 175 | } 176 | }, 177 | "graceful-fs": { 178 | "version": "4.1.11", 179 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 180 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" 181 | }, 182 | "hosted-git-info": { 183 | "version": "2.6.0", 184 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", 185 | "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" 186 | }, 187 | "inflight": { 188 | "version": "1.0.6", 189 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 190 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 191 | "requires": { 192 | "once": "^1.3.0", 193 | "wrappy": "1" 194 | } 195 | }, 196 | "inherits": { 197 | "version": "2.0.3", 198 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 199 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 200 | }, 201 | "invert-kv": { 202 | "version": "1.0.0", 203 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", 204 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" 205 | }, 206 | "is-arrayish": { 207 | "version": "0.2.1", 208 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 209 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" 210 | }, 211 | "is-builtin-module": { 212 | "version": "1.0.0", 213 | "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", 214 | "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", 215 | "requires": { 216 | "builtin-modules": "^1.0.0" 217 | } 218 | }, 219 | "is-fullwidth-code-point": { 220 | "version": "1.0.0", 221 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 222 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 223 | "requires": { 224 | "number-is-nan": "^1.0.0" 225 | } 226 | }, 227 | "is-hex-prefixed": { 228 | "version": "1.0.0", 229 | "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", 230 | "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" 231 | }, 232 | "is-utf8": { 233 | "version": "0.2.1", 234 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 235 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" 236 | }, 237 | "js-sha3": { 238 | "version": "0.5.5", 239 | "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz", 240 | "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=" 241 | }, 242 | "json-schema-traverse": { 243 | "version": "0.3.1", 244 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 245 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 246 | }, 247 | "jsonfile": { 248 | "version": "2.4.0", 249 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", 250 | "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", 251 | "requires": { 252 | "graceful-fs": "^4.1.6" 253 | } 254 | }, 255 | "klaw": { 256 | "version": "1.3.1", 257 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", 258 | "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", 259 | "requires": { 260 | "graceful-fs": "^4.1.9" 261 | } 262 | }, 263 | "lcid": { 264 | "version": "1.0.0", 265 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", 266 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", 267 | "requires": { 268 | "invert-kv": "^1.0.0" 269 | } 270 | }, 271 | "load-json-file": { 272 | "version": "1.1.0", 273 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", 274 | "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", 275 | "requires": { 276 | "graceful-fs": "^4.1.2", 277 | "parse-json": "^2.2.0", 278 | "pify": "^2.0.0", 279 | "pinkie-promise": "^2.0.0", 280 | "strip-bom": "^2.0.0" 281 | } 282 | }, 283 | "lodash": { 284 | "version": "4.17.10", 285 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 286 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" 287 | }, 288 | "lodash.assign": { 289 | "version": "4.2.0", 290 | "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", 291 | "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" 292 | }, 293 | "memorystream": { 294 | "version": "0.3.1", 295 | "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", 296 | "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" 297 | }, 298 | "minimatch": { 299 | "version": "3.0.4", 300 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 301 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 302 | "requires": { 303 | "brace-expansion": "^1.1.7" 304 | } 305 | }, 306 | "ms": { 307 | "version": "2.0.0", 308 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 309 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 310 | }, 311 | "normalize-package-data": { 312 | "version": "2.4.0", 313 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", 314 | "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", 315 | "requires": { 316 | "hosted-git-info": "^2.1.4", 317 | "is-builtin-module": "^1.0.0", 318 | "semver": "2 || 3 || 4 || 5", 319 | "validate-npm-package-license": "^3.0.1" 320 | } 321 | }, 322 | "number-is-nan": { 323 | "version": "1.0.1", 324 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 325 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 326 | }, 327 | "number-to-bn": { 328 | "version": "1.7.0", 329 | "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", 330 | "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", 331 | "requires": { 332 | "bn.js": "4.11.6", 333 | "strip-hex-prefix": "1.0.0" 334 | } 335 | }, 336 | "once": { 337 | "version": "1.4.0", 338 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 339 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 340 | "requires": { 341 | "wrappy": "1" 342 | } 343 | }, 344 | "os-locale": { 345 | "version": "1.4.0", 346 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", 347 | "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", 348 | "requires": { 349 | "lcid": "^1.0.0" 350 | } 351 | }, 352 | "parse-json": { 353 | "version": "2.2.0", 354 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 355 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 356 | "requires": { 357 | "error-ex": "^1.2.0" 358 | } 359 | }, 360 | "path-exists": { 361 | "version": "2.1.0", 362 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", 363 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", 364 | "requires": { 365 | "pinkie-promise": "^2.0.0" 366 | } 367 | }, 368 | "path-is-absolute": { 369 | "version": "1.0.1", 370 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 371 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 372 | }, 373 | "path-type": { 374 | "version": "1.1.0", 375 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", 376 | "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", 377 | "requires": { 378 | "graceful-fs": "^4.1.2", 379 | "pify": "^2.0.0", 380 | "pinkie-promise": "^2.0.0" 381 | } 382 | }, 383 | "pify": { 384 | "version": "2.3.0", 385 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 386 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" 387 | }, 388 | "pinkie": { 389 | "version": "2.0.4", 390 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 391 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" 392 | }, 393 | "pinkie-promise": { 394 | "version": "2.0.1", 395 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 396 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 397 | "requires": { 398 | "pinkie": "^2.0.0" 399 | } 400 | }, 401 | "read-pkg": { 402 | "version": "1.1.0", 403 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", 404 | "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", 405 | "requires": { 406 | "load-json-file": "^1.0.0", 407 | "normalize-package-data": "^2.3.2", 408 | "path-type": "^1.0.0" 409 | } 410 | }, 411 | "read-pkg-up": { 412 | "version": "1.0.1", 413 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", 414 | "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", 415 | "requires": { 416 | "find-up": "^1.0.0", 417 | "read-pkg": "^1.0.0" 418 | } 419 | }, 420 | "require-directory": { 421 | "version": "2.1.1", 422 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 423 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 424 | }, 425 | "require-from-string": { 426 | "version": "1.2.1", 427 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", 428 | "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=" 429 | }, 430 | "require-main-filename": { 431 | "version": "1.0.1", 432 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 433 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" 434 | }, 435 | "rimraf": { 436 | "version": "2.6.2", 437 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 438 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 439 | "requires": { 440 | "glob": "^7.0.5" 441 | } 442 | }, 443 | "semver": { 444 | "version": "5.5.0", 445 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 446 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" 447 | }, 448 | "set-blocking": { 449 | "version": "2.0.0", 450 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 451 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 452 | }, 453 | "solc": { 454 | "version": "0.4.24", 455 | "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.24.tgz", 456 | "integrity": "sha512-2xd7Cf1HeVwrIb6Bu1cwY2/TaLRodrppCq3l7rhLimFQgmxptXhTC3+/wesVLpB09F1A2kZgvbMOgH7wvhFnBQ==", 457 | "requires": { 458 | "fs-extra": "^0.30.0", 459 | "memorystream": "^0.3.1", 460 | "require-from-string": "^1.1.0", 461 | "semver": "^5.3.0", 462 | "yargs": "^4.7.1" 463 | } 464 | }, 465 | "solc-js": { 466 | "version": "0.4.20-browser.1", 467 | "resolved": "https://registry.npmjs.org/solc-js/-/solc-js-0.4.20-browser.1.tgz", 468 | "integrity": "sha512-tR6Y/gy3Q/GyUrxKe7kHkwTR3bRhpKFyNg4afG7xi4G15DzTKTEfvxM+xElrqHvr4p6Yd26KN4NIWWc+9t2ENg==", 469 | "requires": { 470 | "fs-extra": "^0.30.0", 471 | "memorystream": "^0.3.1", 472 | "require-from-string": "^1.1.0", 473 | "semver": "^5.3.0", 474 | "yargs": "^4.7.1" 475 | } 476 | }, 477 | "spdx-correct": { 478 | "version": "3.0.0", 479 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", 480 | "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", 481 | "requires": { 482 | "spdx-expression-parse": "^3.0.0", 483 | "spdx-license-ids": "^3.0.0" 484 | } 485 | }, 486 | "spdx-exceptions": { 487 | "version": "2.1.0", 488 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", 489 | "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" 490 | }, 491 | "spdx-expression-parse": { 492 | "version": "3.0.0", 493 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 494 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 495 | "requires": { 496 | "spdx-exceptions": "^2.1.0", 497 | "spdx-license-ids": "^3.0.0" 498 | } 499 | }, 500 | "spdx-license-ids": { 501 | "version": "3.0.0", 502 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", 503 | "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" 504 | }, 505 | "string-width": { 506 | "version": "1.0.2", 507 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 508 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 509 | "requires": { 510 | "code-point-at": "^1.0.0", 511 | "is-fullwidth-code-point": "^1.0.0", 512 | "strip-ansi": "^3.0.0" 513 | } 514 | }, 515 | "strip-ansi": { 516 | "version": "3.0.1", 517 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 518 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 519 | "requires": { 520 | "ansi-regex": "^2.0.0" 521 | } 522 | }, 523 | "strip-bom": { 524 | "version": "2.0.0", 525 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", 526 | "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", 527 | "requires": { 528 | "is-utf8": "^0.2.0" 529 | } 530 | }, 531 | "strip-hex-prefix": { 532 | "version": "1.0.0", 533 | "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", 534 | "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", 535 | "requires": { 536 | "is-hex-prefixed": "1.0.0" 537 | } 538 | }, 539 | "truffle-artifactor": { 540 | "version": "3.0.5", 541 | "resolved": "https://registry.npmjs.org/truffle-artifactor/-/truffle-artifactor-3.0.5.tgz", 542 | "integrity": "sha512-nDCdAUTJ9CGDSvO6wBZbor0VtaGx0lOS+AC6qNW4njrN/rF7niKXdz7YRpiNX6snVsUPprY0gVZrgi02f4d+uw==", 543 | "requires": { 544 | "async": "^1.5.2", 545 | "debug": "^3.1.0", 546 | "fs-extra": "^1.0.0", 547 | "lodash": "^4.11.2", 548 | "truffle-contract": "^3.0.5", 549 | "truffle-contract-schema": "^2.0.0", 550 | "truffle-expect": "^0.0.3" 551 | }, 552 | "dependencies": { 553 | "fs-extra": { 554 | "version": "1.0.0", 555 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", 556 | "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", 557 | "requires": { 558 | "graceful-fs": "^4.1.2", 559 | "jsonfile": "^2.1.0", 560 | "klaw": "^1.0.0" 561 | } 562 | } 563 | } 564 | }, 565 | "truffle-blockchain-utils": { 566 | "version": "0.0.4", 567 | "resolved": "https://registry.npmjs.org/truffle-blockchain-utils/-/truffle-blockchain-utils-0.0.4.tgz", 568 | "integrity": "sha512-wgRrhwqh0aea08Hz28hUV4tuF2uTVQH/e9kBou+WK04cqrutB5cxQVQ6HGjeZLltxBYOFvhrGOOq4l3WJFnPEA==" 569 | }, 570 | "truffle-contract": { 571 | "version": "3.0.5", 572 | "resolved": "https://registry.npmjs.org/truffle-contract/-/truffle-contract-3.0.5.tgz", 573 | "integrity": "sha512-lRayhvX73OrLJ6TDvc0iYbzcB+Y1F9FCj9N1FXQ2EKOkqyIjmgZNMZFHGjvfTws1gsmonYd6sND0ahipiUYy8g==", 574 | "requires": { 575 | "ethjs-abi": "0.1.8", 576 | "truffle-blockchain-utils": "^0.0.4", 577 | "truffle-contract-schema": "^2.0.0", 578 | "truffle-error": "0.0.2", 579 | "web3": "^0.20.1" 580 | } 581 | }, 582 | "truffle-contract-schema": { 583 | "version": "2.0.0", 584 | "resolved": "https://registry.npmjs.org/truffle-contract-schema/-/truffle-contract-schema-2.0.0.tgz", 585 | "integrity": "sha512-nLlspmu1GKDaluWksBwitHi/7Z3IpRjmBYeO9N+T1nVJD2V4IWJaptCKP1NqnPiJA+FChB7+F7pI6Br51/FtXQ==", 586 | "requires": { 587 | "ajv": "^5.1.1", 588 | "crypto-js": "^3.1.9-1", 589 | "debug": "^3.1.0" 590 | } 591 | }, 592 | "truffle-error": { 593 | "version": "0.0.2", 594 | "resolved": "https://registry.npmjs.org/truffle-error/-/truffle-error-0.0.2.tgz", 595 | "integrity": "sha1-AbGJt4UFVmrhaJwjnHyi3RIc/kw=" 596 | }, 597 | "truffle-expect": { 598 | "version": "0.0.3", 599 | "resolved": "https://registry.npmjs.org/truffle-expect/-/truffle-expect-0.0.3.tgz", 600 | "integrity": "sha1-m3XO80O9WW5+XbyHj18bLjGKlEw=" 601 | }, 602 | "utf8": { 603 | "version": "2.1.2", 604 | "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", 605 | "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" 606 | }, 607 | "validate-npm-package-license": { 608 | "version": "3.0.3", 609 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", 610 | "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", 611 | "requires": { 612 | "spdx-correct": "^3.0.0", 613 | "spdx-expression-parse": "^3.0.0" 614 | } 615 | }, 616 | "web3": { 617 | "version": "0.20.6", 618 | "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.6.tgz", 619 | "integrity": "sha1-PpcwauAk+yThCj11yIQwJWIhUSA=", 620 | "requires": { 621 | "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", 622 | "crypto-js": "^3.1.4", 623 | "utf8": "^2.1.1", 624 | "xhr2": "*", 625 | "xmlhttprequest": "*" 626 | }, 627 | "dependencies": { 628 | "bignumber.js": { 629 | "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", 630 | "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git" 631 | }, 632 | "crypto-js": { 633 | "version": "3.1.8", 634 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", 635 | "integrity": "sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU=" 636 | } 637 | } 638 | }, 639 | "which-module": { 640 | "version": "1.0.0", 641 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", 642 | "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" 643 | }, 644 | "window-size": { 645 | "version": "0.2.0", 646 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", 647 | "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" 648 | }, 649 | "wrap-ansi": { 650 | "version": "2.1.0", 651 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 652 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 653 | "requires": { 654 | "string-width": "^1.0.1", 655 | "strip-ansi": "^3.0.1" 656 | } 657 | }, 658 | "wrappy": { 659 | "version": "1.0.2", 660 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 661 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 662 | }, 663 | "xhr2": { 664 | "version": "0.1.4", 665 | "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.4.tgz", 666 | "integrity": "sha1-f4dliEdxbbUCYyOBL4GMras4el8=" 667 | }, 668 | "xmlhttprequest": { 669 | "version": "1.8.0", 670 | "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", 671 | "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" 672 | }, 673 | "y18n": { 674 | "version": "3.2.1", 675 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", 676 | "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" 677 | }, 678 | "yargs": { 679 | "version": "4.8.1", 680 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", 681 | "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", 682 | "requires": { 683 | "cliui": "^3.2.0", 684 | "decamelize": "^1.1.1", 685 | "get-caller-file": "^1.0.1", 686 | "lodash.assign": "^4.0.3", 687 | "os-locale": "^1.4.0", 688 | "read-pkg-up": "^1.0.1", 689 | "require-directory": "^2.1.1", 690 | "require-main-filename": "^1.0.1", 691 | "set-blocking": "^2.0.0", 692 | "string-width": "^1.0.1", 693 | "which-module": "^1.0.0", 694 | "window-size": "^0.2.0", 695 | "y18n": "^3.2.1", 696 | "yargs-parser": "^2.4.1" 697 | } 698 | }, 699 | "yargs-parser": { 700 | "version": "2.4.1", 701 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", 702 | "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", 703 | "requires": { 704 | "camelcase": "^3.0.0", 705 | "lodash.assign": "^4.0.6" 706 | } 707 | } 708 | } 709 | } 710 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "bignumber.js": "5.0.0", 11 | "solc": "^0.4.24", 12 | "solc-js": "^0.4.20-browser.1", 13 | "truffle-artifactor": "^3.0.4" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "author": "", 20 | "license": "ISC" 21 | } 22 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/test/tests/erc20.js: -------------------------------------------------------------------------------- 1 | /* 2 | * These tests were adapted from the OpenZepplin repository here: 3 | * 4 | * https://github.com/OpenZeppelin/openzeppelin-solidity 5 | */ 6 | 7 | const th = require('../utils/testHelpers'); 8 | const deployer = require('../utils/deployer.js') 9 | const assertRevert = th.assertRevert; 10 | 11 | contract('StandardToken', function (accounts) { 12 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 13 | const owner = accounts[0]; 14 | const recipient = accounts[1]; 15 | const anotherAccount = accounts[2]; 16 | const mintType = 1; 17 | const assignTokens = async function(token, to, amount) { 18 | await token.addToWhitelist(to, { from: owner }); 19 | await token.mintTokens(mintType, to, 100, {from: owner}); 20 | } 21 | 22 | beforeEach(async function () { 23 | let contract = await deployer.setupContract(accounts); 24 | this.token = contract.fantom; 25 | assert(owner === contract.owner); 26 | await assignTokens(this.token, owner, 100); 27 | await th.setDate(ICOEndTime + 1); 28 | await this.token.makeTradeable(); 29 | }); 30 | 31 | describe('total supply', function () { 32 | it('returns the total amount of tokens immediately after deployment', async function () { 33 | const totalSupply = await this.token.totalSupply(); 34 | 35 | assert.equal(totalSupply, 100); 36 | }); 37 | }); 38 | 39 | describe('balanceOf', function () { 40 | describe('when the requested account has no tokens', function () { 41 | it('returns zero', async function () { 42 | const balance = await this.token.balanceOf(anotherAccount); 43 | 44 | assert.equal(balance, 0); 45 | }); 46 | }); 47 | 48 | describe('when the requested account has some tokens', function () { 49 | it('returns the total amount of tokens', async function () { 50 | const balance = await this.token.balanceOf(owner); 51 | 52 | assert.equal(balance, 100); 53 | }); 54 | }); 55 | }); 56 | 57 | describe('transfer', function () { 58 | describe('when the recipient is not the zero address', function () { 59 | const to = recipient; 60 | 61 | describe('when the sender does not have enough balance', function () { 62 | const amount = 101; 63 | 64 | it('reverts', async function () { 65 | await assertRevert(this.token.transfer(to, amount, { from: owner })); 66 | }); 67 | }); 68 | 69 | describe('when the sender has enough balance', function () { 70 | const amount = 100; 71 | 72 | it('transfers the requested amount', async function () { 73 | await this.token.transfer(to, amount, { from: owner }); 74 | 75 | const senderBalance = await this.token.balanceOf(owner); 76 | assert.equal(senderBalance, 0); 77 | 78 | const recipientBalance = await this.token.balanceOf(to); 79 | assert.equal(recipientBalance, amount); 80 | }); 81 | 82 | it('emits a transfer event', async function () { 83 | const { logs } = await this.token.transfer(to, amount, { from: owner }); 84 | 85 | assert.equal(logs.length, 1); 86 | assert.equal(logs[0].event, 'Transfer'); 87 | assert.equal(logs[0].args._from, owner); 88 | assert.equal(logs[0].args._to, to); 89 | assert(logs[0].args._value.eq(amount)); 90 | }); 91 | }); 92 | }); 93 | 94 | describe('when the recipient is the zero address', function () { 95 | const to = ZERO_ADDRESS; 96 | 97 | it('reverts', async function () { 98 | await assertRevert(this.token.transfer(to, 100, { from: owner })); 99 | }); 100 | }); 101 | }); 102 | 103 | describe('approve', function () { 104 | describe('when the spender is not the zero address', function () { 105 | const spender = recipient; 106 | 107 | describe('when the sender has enough balance', function () { 108 | const amount = 100; 109 | 110 | it('emits an approval event', async function () { 111 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 112 | 113 | assert.equal(logs.length, 1); 114 | assert.equal(logs[0].event, 'Approval'); 115 | assert.equal(logs[0].args._owner, owner); 116 | assert.equal(logs[0].args._spender, spender); 117 | assert(logs[0].args._value.eq(amount)); 118 | }); 119 | 120 | describe('when there was no approved amount before', function () { 121 | it('approves the requested amount', async function () { 122 | await this.token.approve(spender, amount, { from: owner }); 123 | 124 | const allowance = await this.token.allowance(owner, spender); 125 | assert.equal(allowance, amount); 126 | }); 127 | }); 128 | 129 | describe('when the spender had an approved amount', function () { 130 | beforeEach(async function () { 131 | await this.token.approve(spender, 1, { from: owner }); 132 | }); 133 | 134 | it('approves the requested amount and replaces the previous one', async function () { 135 | await this.token.approve(spender, amount, { from: owner }); 136 | 137 | const allowance = await this.token.allowance(owner, spender); 138 | assert.equal(allowance, amount); 139 | }); 140 | }); 141 | }); 142 | 143 | describe('when the sender does not have enough balance', function () { 144 | const amount = 101; 145 | 146 | it('emits an approval event', async function () { 147 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 148 | 149 | assert.equal(logs.length, 1); 150 | assert.equal(logs[0].event, 'Approval'); 151 | assert.equal(logs[0].args._owner, owner); 152 | assert.equal(logs[0].args._spender, spender); 153 | assert(logs[0].args._value.eq(amount)); 154 | }); 155 | 156 | describe('when there was no approved amount before', function () { 157 | it('approves the requested amount', async function () { 158 | await this.token.approve(spender, amount, { from: owner }); 159 | 160 | const allowance = await this.token.allowance(owner, spender); 161 | assert.equal(allowance, amount); 162 | }); 163 | }); 164 | 165 | describe('when the spender had an approved amount', function () { 166 | beforeEach(async function () { 167 | await this.token.approve(spender, 1, { from: owner }); 168 | }); 169 | 170 | it('approves the requested amount and replaces the previous one', async function () { 171 | await this.token.approve(spender, amount, { from: owner }); 172 | 173 | const allowance = await this.token.allowance(owner, spender); 174 | assert.equal(allowance, amount); 175 | }); 176 | }); 177 | }); 178 | }); 179 | 180 | describe('when the spender is the zero address', function () { 181 | const amount = 100; 182 | const spender = ZERO_ADDRESS; 183 | 184 | it('approves the requested amount', async function () { 185 | await this.token.approve(spender, amount, { from: owner }); 186 | 187 | const allowance = await this.token.allowance(owner, spender); 188 | assert.equal(allowance, amount); 189 | }); 190 | 191 | it('emits an approval event', async function () { 192 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 193 | 194 | assert.equal(logs.length, 1); 195 | assert.equal(logs[0].event, 'Approval'); 196 | assert.equal(logs[0].args._owner, owner); 197 | assert.equal(logs[0].args._spender, spender); 198 | assert(logs[0].args._value.eq(amount)); 199 | }); 200 | }); 201 | }); 202 | 203 | describe('transfer from', function () { 204 | const spender = recipient; 205 | 206 | describe('when the recipient is not the zero address', function () { 207 | const to = anotherAccount; 208 | 209 | describe('when the spender has enough approved balance', function () { 210 | beforeEach(async function () { 211 | await this.token.approve(spender, 100, { from: owner }); 212 | }); 213 | 214 | describe('when the owner has enough balance', function () { 215 | const amount = 100; 216 | 217 | it('transfers the requested amount', async function () { 218 | await this.token.transferFrom(owner, to, amount, { from: spender }); 219 | 220 | const senderBalance = await this.token.balanceOf(owner); 221 | assert.equal(senderBalance, 0); 222 | 223 | const recipientBalance = await this.token.balanceOf(to); 224 | assert.equal(recipientBalance, amount); 225 | }); 226 | 227 | it('decreases the spender allowance', async function () { 228 | await this.token.transferFrom(owner, to, amount, { from: spender }); 229 | 230 | const allowance = await this.token.allowance(owner, spender); 231 | assert(allowance.eq(0)); 232 | }); 233 | 234 | it('emits a transfer event', async function () { 235 | const { logs } = await this.token.transferFrom(owner, to, amount, { from: spender }); 236 | 237 | assert.equal(logs.length, 1); 238 | assert.equal(logs[0].event, 'Transfer'); 239 | assert.equal(logs[0].args._from, owner); 240 | assert.equal(logs[0].args._to, to); 241 | assert(logs[0].args._value.eq(amount)); 242 | }); 243 | }); 244 | 245 | describe('when the owner does not have enough balance', function () { 246 | const amount = 101; 247 | 248 | it('reverts', async function () { 249 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 250 | }); 251 | }); 252 | }); 253 | 254 | describe('when the spender does not have enough approved balance', function () { 255 | beforeEach(async function () { 256 | await this.token.approve(spender, 99, { from: owner }); 257 | }); 258 | 259 | describe('when the owner has enough balance', function () { 260 | const amount = 100; 261 | 262 | it('reverts', async function () { 263 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 264 | }); 265 | }); 266 | 267 | describe('when the owner does not have enough balance', function () { 268 | const amount = 101; 269 | 270 | it('reverts', async function () { 271 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 272 | }); 273 | }); 274 | }); 275 | }); 276 | 277 | describe('when the recipient is the zero address', function () { 278 | const amount = 100; 279 | const to = ZERO_ADDRESS; 280 | 281 | beforeEach(async function () { 282 | await this.token.approve(spender, amount, { from: owner }); 283 | }); 284 | 285 | it('reverts', async function () { 286 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 287 | }); 288 | }); 289 | }); 290 | }); 291 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/test/tests/fantom.js: -------------------------------------------------------------------------------- 1 | const deployer = require('../utils/deployer.js') 2 | const helpers = require('../utils/testHelpers.js') 3 | const BigNumber = require('bignumber.js') 4 | 5 | const Fantom = artifacts.require('./FantomToken.sol') 6 | 7 | /* 8 | * Return the amount of seconds in x days 9 | */ 10 | const days = (x) => x * 60 * 60 * 24 11 | assert(days(2) === 172800, 'days() is wrong') 12 | 13 | const hours = (x) => x * 60 * 60 14 | assert(hours(2) == 7200, `hours() is wrong`) 15 | 16 | const minutes = (x) => x * 60 17 | 18 | const dateBySeconds = (x) => new BigNumber(Math.floor(x/1000)) 19 | assert(dateBySeconds(new Date(Date.UTC(2018, 00, 01, 10, 10, 10))).cmp((new BigNumber(1514801410))) == 0, 'dateBySeconds() is wrong') 20 | 21 | /* 22 | * Return the timestamp of the current block 23 | */ 24 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp) 25 | 26 | contract('LockSlots', (accounts) => { 27 | 28 | it('[isAvailableLockSlot] should return true for account no locked slot', async () => { 29 | const rig = await deployer.setupContract(accounts) 30 | const holder = accounts[2] 31 | 32 | let slotStatus = await rig.fantom.isAvailableLockSlot(holder, 33 | dateBySeconds(new Date(2018, 04, 23, 23, 01, 01))) 34 | 35 | assert(slotStatus == true, `Lock Status, expected true; got ${slotStatus}`) 36 | 37 | // Check in the past 38 | slotStatus = await rig.fantom.isAvailableLockSlot(holder, 39 | dateBySeconds(new Date(2017, 04, 23, 23, 01, 01))) 40 | 41 | assert(slotStatus == true, `Lock Status, expected true; got ${slotStatus}`) 42 | }) 43 | 44 | it('[mintTokensLocked] should lock correct number of tokens', async () => { 45 | const rig = await deployer.setupContract(accounts) 46 | const owner = rig.owner 47 | const fantom = rig.fantom 48 | const lockedHolder = accounts[2] 49 | 50 | // whitelist the account 51 | await fantom.addToWhitelist(lockedHolder, {from: owner}) 52 | 53 | assert(true == await fantom.whitelist.call(lockedHolder), 'Whitelist status incorrect') 54 | 55 | // mint locked tokens for account 56 | let dateToLock = rig.ICOStartTime + days(5) 57 | await fantom.mintTokensLocked(1, lockedHolder, 10, dateToLock, {from: owner}) 58 | 59 | // check the number of locked tokens 60 | let lockedTokens = await fantom.lockedTokens(lockedHolder) 61 | 62 | assert(lockedTokens.cmp(new BigNumber(10)) == 0, `Number of minted tokens locked; expected 10; Got: ${lockedTokens}`) 63 | }) 64 | 65 | it('[mintTokensLockedMultiple] mint the correct amount of locked tokens', async () => { 66 | const rig = await deployer.setupContract(accounts) 67 | const owner = rig.owner 68 | const fantom = rig.fantom 69 | const startDate = rig.ICOStartTime 70 | const lockedHolder = accounts[2] 71 | 72 | // whitelist the account 73 | await fantom.addToWhitelist(lockedHolder, {from: owner}) 74 | 75 | let initialDate = new BigNumber(startDate + days(1)) 76 | 77 | tokensToMint = [5,4,3,2,1] 78 | terms = [ 79 | initialDate.plus(days(1)), 80 | initialDate.plus(days(2)), 81 | initialDate.plus(days(3)), 82 | initialDate.plus(days(4)), 83 | initialDate.plus(days(5)) 84 | ] 85 | 86 | accounts = [ 87 | lockedHolder, 88 | lockedHolder, 89 | lockedHolder, 90 | lockedHolder, 91 | lockedHolder 92 | ] 93 | 94 | await fantom.mintTokensLockedMultiple(1, accounts, tokensToMint, terms, {from: owner}) 95 | 96 | let lockedTokens = await fantom.lockedTokens(lockedHolder) 97 | 98 | assert(lockedTokens.cmp(new BigNumber(15)) == 0, `Number of minted tokens locked; expected 15; Got: ${lockedTokens}`) 99 | }) 100 | 101 | it('[mintTokensLockedMultiple] number of lockslots fill correctly', async () => { 102 | const rig = await deployer.setupContract(accounts) 103 | const owner = rig.owner 104 | const fantom = rig.fantom 105 | const startDate = rig.ICOStartTime 106 | const lockedHolder = accounts[2] 107 | 108 | // whitelist the account 109 | await fantom.addToWhitelist(lockedHolder, {from: owner}) 110 | 111 | let initialDate = new BigNumber(startDate + days(1)) 112 | tokensToMint = [5,4,3,2,1] 113 | terms = [ 114 | initialDate.plus(days(1)), 115 | initialDate.plus(days(2)), 116 | initialDate.plus(days(3)), 117 | initialDate.plus(days(4)), 118 | initialDate.plus(days(5)) 119 | ] 120 | 121 | accounts = [ 122 | lockedHolder, 123 | lockedHolder, 124 | lockedHolder, 125 | lockedHolder, 126 | lockedHolder 127 | ] 128 | 129 | await fantom.mintTokensLockedMultiple(1, accounts, tokensToMint, terms, {from: owner}) 130 | 131 | let lockedTokens = await fantom.lockedTokens(lockedHolder) 132 | 133 | assert(lockedTokens.cmp(new BigNumber(15)) == 0, `Number of minted tokens locked; expected 15; Got: ${lockedTokens}`) 134 | 135 | // Available Lock Slots 136 | let slotStatus = await rig.fantom.isAvailableLockSlot(lockedHolder, 137 | initialDate.plus(days(2))) 138 | 139 | assert(true == slotStatus, "Lock slot available for same term") 140 | 141 | slotStatus = await rig.fantom.isAvailableLockSlot(lockedHolder, 142 | initialDate.plus(days(6))) 143 | 144 | assert(false == slotStatus, "Lock slot not available for extra term") 145 | 146 | }) 147 | 148 | 149 | it('[mintTokens] should not lock the tokens', async () => { 150 | const rig = await deployer.setupContract(accounts) 151 | const owner = rig.owner 152 | const fantom = rig.fantom 153 | const holder = accounts[2] 154 | 155 | // whitelist the account 156 | await fantom.addToWhitelist(holder, {from: owner}) 157 | 158 | await fantom.mintTokens(1, holder, 10, {from: owner}) 159 | 160 | let lockedTokens = await fantom.lockedTokens(holder) 161 | 162 | assert(lockedTokens.cmp(new BigNumber(0)) == 0, `Number of minted tokens locked; expected 0; Got: ${lockedTokens}`) 163 | 164 | let unlockedTokens = await fantom.unlockedTokens(holder) 165 | assert(unlockedTokens.cmp(new BigNumber(10)) == 0, `Number of minted tokens unlocked; expected 10; Got: ${lockedTokens}`) 166 | }) 167 | }) 168 | 169 | 170 | contract('FantomICODates', (accounts) => { 171 | 172 | 173 | it('should not allow public to change dates', async () => { 174 | const rig = await deployer.setupContract(accounts) 175 | const icoStartTime = rig.ICOStartTime 176 | 177 | let newD = icoStartTime - 10 // Must be before the current start date. 178 | 179 | await helpers.assertRevert(rig.fantom.setDateMainStart(newD, {from: accounts[1]})) 180 | }) 181 | 182 | it('should allow owner to change the dates', async () => { 183 | 184 | const rig = await deployer.setupContract(accounts) 185 | const icoStartTime = rig.ICOStartTime 186 | 187 | let newD = new BigNumber(icoStartTime - 10) // Must be before the current start date. 188 | 189 | await rig.fantom.setDateMainEnd(newD.plus(days(10)), {from: rig.owner}) 190 | await rig.fantom.setDateMainStart(newD, {from: rig.owner}) 191 | 192 | assert(newD.cmp(await rig.fantom.dateMainStart.call()) == 0, 'start date set') 193 | assert(newD.plus(days(10)).cmp(await rig.fantom.dateMainEnd.call()) == 0, 'end date set') 194 | }) 195 | 196 | it('should not allow owner to change dates into past', async() => { 197 | const rig = await deployer.setupContract(accounts) 198 | const icoStartTime = rig.ICOStartTime 199 | 200 | let pastD = new BigNumber(icoStartTime - days(360)) // Must be before the current start date. 201 | 202 | await helpers.assertRevert(rig.fantom.setDateMainEnd(pastD, {from: rig.owner})) 203 | }) 204 | 205 | it('should not allow owner to set start date after or equal to end date', async () => { 206 | 207 | const rig = await deployer.setupContract(accounts) 208 | const icoStartTime = rig.ICOStartTime 209 | 210 | let newD = new BigNumber(icoStartTime) // Must be before the current start date. 211 | 212 | 213 | await rig.fantom.setDateMainEnd(newD.plus(days(10)), {from: rig.owner}) 214 | await helpers.assertRevert(rig.fantom.setDateMainStart(newD.plus(days(11)), {from: rig.owner})) 215 | await helpers.assertRevert(rig.fantom.setDateMainStart(newD.plus(days(10)), {from: rig.owner})) 216 | await rig.fantom.setDateMainStart(newD.plus(days(10)).minus(1), {from: rig.owner}) 217 | 218 | 219 | assert(newD.plus(days(10)).cmp(await rig.fantom.dateMainEnd.call()) == 0, 'end date set') 220 | assert(newD.plus(days(10)).minus(1).cmp(await rig.fantom.dateMainStart.call()) == 0, 'start date set') 221 | }) 222 | 223 | it('[mainsale] should detect main period', async () => { 224 | 225 | const rig = await deployer.setupContract(accounts) 226 | const icoStartTime = rig.icoStartTime 227 | 228 | assert(false == (await rig.fantom.isMainFirstDay()), 'Main should not have started') 229 | 230 | helpers.setDate(timestamp().plus(1000)) 231 | 232 | assert(true == (await rig.fantom.isMainFirstDay()), 'Can detect first day of main') 233 | assert(true == (await rig.fantom.isMain()), 'Can detect main') 234 | 235 | helpers.setDate(timestamp().plus(days(2))) 236 | 237 | assert(false == (await rig.fantom.isMainFirstDay()), 'first day of main finished') 238 | assert(true == (await rig.fantom.isMain()), 'Can detect main') 239 | 240 | helpers.setDate(timestamp().plus(days(14))) 241 | 242 | assert(false == (await rig.fantom.isMainFirstDay()), 'Can detect first day of main') 243 | assert(false == (await rig.fantom.isMain()), 'Can detect main') 244 | }) 245 | 246 | }) 247 | 248 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/test/tests/gas-consumption.js: -------------------------------------------------------------------------------- 1 | const th = require('../utils/testHelpers'); 2 | const deployer = require('../utils/deployer.js') 3 | const BigNumber = require('bignumber.js') 4 | const Fantom = artifacts.require('./FantomToken.sol') 5 | 6 | // extra global variables 7 | const assertRevert = th.assertRevert; 8 | const blockGasLimit = 8e6; 9 | const days = 3600*24 10 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp); 11 | 12 | const assignTokens = async function(token, to, amount, owner) { 13 | await token.addToWhitelist(to, { from: owner }); 14 | await token.mintTokens(1, to, amount, {from: owner}); 15 | } 16 | 17 | // Setup a function to test gas usage for many Locked Tokens minting 18 | var testMintLockedTokens = function(numberOfAccounts, accounts) { 19 | 20 | it(`[MintTokensLockedMultiple] should cost less than the block gas limit for ${numberOfAccounts} accounts`, async () => { 21 | // Use the modified contract as date needs to be dynamic 22 | let deployedObject = await deployer.setupContract(accounts); 23 | let deployed = deployedObject.fantom; 24 | 25 | // owner to mint tokens. 26 | // set the lock time to the current time plus 3 days 27 | let lockTime = timestamp().plus(3 * days) 28 | let amountOfTokensToLock = 10e5 29 | let mintType = 1 30 | let account = deployedObject.owner // this should already be whitelisted 31 | // white list the owner 32 | await deployed.addToWhitelist(account, {from: deployedObject.owner}) 33 | 34 | let accountList = [] 35 | let amounts = [] 36 | let lockTimeList = [] 37 | 38 | for (i=0; i< numberOfAccounts; i++) { 39 | accountList.push(account); 40 | amounts.push(amountOfTokensToLock* (i+1)*10e5); 41 | lockTimeList.push(lockTime); 42 | } 43 | 44 | let tx = await deployed.mintTokensLockedMultiple(mintType, accountList, amounts, lockTimeList) 45 | 46 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 47 | console.log(`Minting locked tokens for ${numberOfAccounts} accounts. Gas Estimate: ${tx.receipt.gasUsed}`); 48 | 49 | }) 50 | 51 | } 52 | 53 | // Setup a function to test for multiple transfers 54 | var testTransferMultiple = function(numberOfAccounts, accounts) { 55 | 56 | it(`[TransferMultiple] should cost less than the block gas limit for ${numberOfAccounts} accounts`, async () => { 57 | // set up balances 58 | let contract = await deployer.setupContract(accounts); 59 | let deployed = contract.fantom; 60 | let owner = contract.owner 61 | assert(owner === contract.owner); 62 | await assignTokens(deployed, owner, 100e7, owner); 63 | await th.setDate(contract.ICOEndTime + 1); 64 | await deployed.makeTradeable(); 65 | 66 | // build account list 67 | let accountList = [] 68 | let amounts = [] 69 | 70 | for (i=0; i< numberOfAccounts; i++) { 71 | accountList.push(accounts[1]); 72 | amounts.push((i)*1e5); 73 | } 74 | 75 | let tx = await deployed.transferMultiple(accountList, amounts) 76 | 77 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 78 | console.log(`Multiple transfer to ${numberOfAccounts} accounts. Gas Estimate: ${tx.receipt.gasUsed}`); 79 | 80 | }) 81 | 82 | } 83 | 84 | contract('Gas Consumption Tests (optimized-runs = 200)', (accounts) => { 85 | 86 | it("Deployment of contract gas estimate", async () => { 87 | // Use the modified contract as date needs to be dynamic 88 | let deployedObject = await deployer.setupContract(accounts); 89 | let deployed = deployedObject.fantom; 90 | let receipt = await web3.eth.getTransactionReceipt(deployed.transactionHash); 91 | 92 | it("should cost less than the block gas limit to deploy", () => { 93 | assert.isBelow(receipt.gasUsed, blockGasLimit) 94 | }) 95 | console.log("Deployment Gas Estimate: " + receipt.gasUsed); 96 | 97 | }); 98 | 99 | it("should cost less than the block gas limit to buy tokens (optimize-runs = 200)", async () => { 100 | 101 | let deployedObject = await deployer.setupContract(accounts); 102 | let deployed = deployedObject.fantom; 103 | let account = deployedObject.owner // this should already be whitelisted 104 | // white list the owner 105 | await deployed.addToWhitelist(account, {from: deployedObject.owner}) 106 | 107 | // set the time into the ICO Start time 108 | await th.setDate(deployedObject.ICOStartTime + 0.2*days); 109 | 110 | let tx = await deployed.sendTransaction({from: account, value: web3.toWei(2,"ether")}); 111 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 112 | console.log("Buy Tokens Gas Estimate: " + tx.receipt.gasUsed); 113 | }); 114 | 115 | it("should cost less than the block gas limit to mint tokens (optimize-runs = 200)", async () => { 116 | // Use the modified contract as date needs to be dynamic 117 | let deployedObject = await deployer.setupContract(accounts); 118 | let deployed = deployedObject.fantom; 119 | 120 | // owner to mint tokens. 121 | // set the lock time to the current time plus 3 days 122 | let lockTime = timestamp().plus(3 * days) 123 | let amountOfTokensToLock = 10 124 | let mintType = 1 125 | let account = deployedObject.owner // this should already be whitelisted 126 | 127 | // white list the owner 128 | await deployed.addToWhitelist(account, {from: deployedObject.owner}) 129 | 130 | let tx = await deployed.mintTokensLocked(mintType, account, amountOfTokensToLock, lockTime) 131 | 132 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 133 | console.log("Minting Locked Tokens Gas Estimate: " + tx.receipt.gasUsed); 134 | }); 135 | 136 | // test the gas costs of minting to mulitple users 137 | testMintLockedTokens(2,accounts) 138 | testMintLockedTokens(5,accounts) 139 | testMintLockedTokens(10,accounts) 140 | testMintLockedTokens(15,accounts) 141 | testMintLockedTokens(20,accounts) 142 | testMintLockedTokens(30,accounts) 143 | testMintLockedTokens(50,accounts) 144 | 145 | // test the gas costs of multiple transfers 146 | testTransferMultiple(2,accounts) 147 | testTransferMultiple(5,accounts) 148 | testTransferMultiple(10,accounts) 149 | testTransferMultiple(15,accounts) 150 | testTransferMultiple(20,accounts) 151 | testTransferMultiple(30,accounts) 152 | testTransferMultiple(50,accounts) 153 | }); 154 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/test/tests/token-math.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require("bignumber.js") 2 | const helpers = require('../utils/testHelpers.js') 3 | const deployer = require('../utils/deployer.js') 4 | 5 | const decimals = 1e18 6 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp) 7 | const tokenRate = 10000 8 | const tokenSupply = 1000000000*decimals 9 | const minContrib = 0.5*decimals 10 | const tokenMainCap = 600000000*decimals 11 | 12 | 13 | const sendEther = (contract, account, etherAmount) => { 14 | let weiSent = etherAmount*decimals 15 | return contract.sendTransaction({from:account, value: weiSent}); 16 | } 17 | 18 | contract('[FantomToken - Token Math]', function(accounts) { 19 | 20 | it('should have the correct caps', async function() { 21 | let contract = await deployer.setupContract(accounts) 22 | let fantom = contract.fantom 23 | 24 | let actualTS = await fantom.TOKEN_TOTAL_SUPPLY.call() 25 | let actualTMC = await fantom.TOKEN_MAIN_CAP.call() 26 | let actualMC = await fantom.MINIMUM_CONTRIBUTION.call() 27 | 28 | assert.equal(tokenSupply, actualTS) 29 | assert.equal(minContrib, actualMC) 30 | assert.equal(tokenMainCap, actualTMC) 31 | }) 32 | 33 | it('should have a correct token rate for 1 ether', async function() { 34 | let contract = await deployer.setupContract(accounts) 35 | let fantom = contract.fantom 36 | let owner = contract.owner 37 | 38 | // white list account 39 | await fantom.addToWhitelist(accounts[1], {from: owner}) 40 | icodate = contract.ICOStartTime + 200 41 | await helpers.setDate(icodate) 42 | 43 | let etherSent = 1 44 | let weiSent = etherSent*decimals 45 | 46 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 47 | let expectedTokens = tokenRate*etherSent*decimals 48 | 49 | let actualTokens = await fantom.balanceOf(accounts[1]); 50 | assert.equal(expectedTokens,actualTokens.toNumber()) 51 | 52 | }) 53 | 54 | it('should not give more tokens than allowed during first day', async function() { 55 | let contract = await deployer.setupContract(accounts) 56 | let fantom = contract.fantom 57 | let owner = contract.owner 58 | 59 | // white list account 60 | await fantom.addToWhitelist(accounts[1], {from: owner}) 61 | icodate = contract.ICOStartTime + 2000 62 | await helpers.setDate(icodate) 63 | 64 | let whitelisted = await fantom.numberWhitelisted.call() 65 | let tokenLimit = tokenMainCap/whitelisted 66 | let ethLimit = tokenLimit/tokenRate/decimals 67 | let etherSent = ethLimit + 0.001 68 | let weiSent = etherSent*decimals 69 | 70 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 71 | let expectedTokens = tokenMainCap 72 | 73 | let actualTokens = await fantom.balanceOf(accounts[1]); 74 | assert.equal(expectedTokens,actualTokens.toNumber()) 75 | }) 76 | 77 | it('a user should not be able to purchase more than the token limit in the first day', async function() { 78 | let contract = await deployer.setupContract(accounts) 79 | let fantom = contract.fantom 80 | let owner = contract.owner 81 | 82 | // white list account 83 | await fantom.addToWhitelist(accounts[1], {from: owner}) 84 | icodate = contract.ICOStartTime + 2000 85 | await helpers.setDate(icodate) 86 | 87 | let whitelisted = await fantom.numberWhitelisted.call() 88 | let tokenLimit = tokenMainCap/whitelisted 89 | let ethLimit = tokenLimit/tokenRate/decimals 90 | let etherSent = ethLimit + 0.001 91 | let weiSent = etherSent*decimals 92 | 93 | // send more than the maximum 94 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 95 | // then send more 96 | helpers.assertRevert(fantom.sendTransaction({from:accounts[1], value: weiSent})) 97 | }) 98 | 99 | it('should purchase the main total cap if every whitelisted users send ether over their cap', async function() { 100 | let contract = await deployer.setupContract(accounts) 101 | let fantom = contract.fantom 102 | let owner = contract.owner 103 | 104 | // white list account 105 | await fantom.addToWhitelist(accounts[1], {from: owner}) 106 | await fantom.addToWhitelist(accounts[2], {from: owner}) 107 | await fantom.addToWhitelist(accounts[3], {from: owner}) 108 | await fantom.addToWhitelist(accounts[4], {from: owner}) 109 | await fantom.addToWhitelist(accounts[5], {from: owner}) 110 | 111 | icodate = contract.ICOStartTime + 2000 112 | await helpers.setDate(icodate) 113 | 114 | let whitelisted = await fantom.numberWhitelisted.call() 115 | let tokenLimit = tokenMainCap/whitelisted 116 | let ethLimit = tokenLimit/tokenRate/decimals 117 | let etherSent = ethLimit + 0.001 118 | let weiSent = etherSent*decimals 119 | 120 | // send more than the maximum 121 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 122 | await fantom.sendTransaction({from:accounts[2], value: weiSent}); 123 | await fantom.sendTransaction({from:accounts[3], value: weiSent}); 124 | await fantom.sendTransaction({from:accounts[4], value: weiSent}); 125 | await fantom.sendTransaction({from:accounts[5], value: weiSent}); 126 | 127 | let actualTokens = await fantom.totalSupply(); 128 | let expectedTokens = tokenMainCap 129 | 130 | assert.equal(expectedTokens,actualTokens.toNumber()) 131 | }) 132 | 133 | 134 | it('[Scenario 1] should give the correct token amounts for scenario 1', async function() { 135 | 136 | let contract = await deployer.setupContract(accounts) 137 | let fantom = contract.fantom 138 | let owner = contract.owner 139 | 140 | // white list account 141 | await fantom.addToWhitelist(accounts[1], {from: owner}) 142 | await fantom.addToWhitelist(accounts[2], {from: owner}) 143 | await fantom.addToWhitelist(accounts[3], {from: owner}) 144 | await fantom.addToWhitelist(accounts[4], {from: owner}) 145 | await fantom.addToWhitelist(accounts[5], {from: owner}) 146 | // set start time 147 | let icodate = contract.ICOStartTime + 2000 148 | await helpers.setDate(icodate) 149 | 150 | // list of ether to be sent 151 | ethSent = [30, 176, 1e3,12,2] 152 | // list of tokens we expect as a result 153 | expectedTokens = ethSent.map(x => x*tokenRate*decimals) 154 | 155 | // account 1 spends 30 eth during first day 156 | await sendEther(fantom, accounts[1], 30) 157 | // account 2 spends 176 eth during first day 158 | await sendEther(fantom, accounts[2], 176) 159 | // account 3 spends 1000 eth during first day 160 | await sendEther(fantom, accounts[3], 1e3) 161 | 162 | // Move to after the first day 163 | await helpers.setDate(contract.ICOStartTime+ 3600*24*2) 164 | 165 | // account 4 spends 12 eth after the first day 166 | await sendEther(fantom, accounts[4], 12) 167 | // account 5 spends 2 eth after the first day 168 | await sendEther(fantom, accounts[5], 2) 169 | 170 | actualTokenBalances = [] 171 | for (i=1; i<=5; i++) { 172 | actualTokenBalances.push((await fantom.balanceOf(accounts[i])).toNumber()); 173 | } 174 | 175 | for (i=1; i<=5; i++) { 176 | assert(expectedTokens[i] == actualTokenBalances[i], "token balances are not as expected") 177 | } 178 | }) 179 | 180 | it('[Scenario 2] should give the correct token amounts for scenario 1', async function() { 181 | 182 | let contract = await deployer.setupContract(accounts) 183 | let fantom = contract.fantom 184 | let owner = contract.owner 185 | 186 | // white list account 187 | await fantom.addToWhitelist(accounts[1], {from: owner}) 188 | await fantom.addToWhitelist(accounts[2], {from: owner}) 189 | await fantom.addToWhitelist(accounts[3], {from: owner}) 190 | await fantom.addToWhitelist(accounts[4], {from: owner}) 191 | await fantom.addToWhitelist(accounts[5], {from: owner}) 192 | // set start time 193 | let icodate = contract.ICOStartTime + 2000 194 | await helpers.setDate(icodate) 195 | 196 | // list of ether to be sent 197 | ethSent = [10e6, 176, 1e3,12,2] 198 | // list of tokens we expect as a result 199 | expectedTokens = ethSent.map(x => x*tokenRate*decimals) 200 | // first day limit 201 | let firstDayLimit = tokenMainCap/5 202 | // first user hits their cap 203 | expectedTokens[0] = firstDayLimit 204 | 205 | // account 1 spends 10e6 eth during first day 206 | await sendEther(fantom, accounts[1], 30) 207 | // account 2 spends 176 eth during first day 208 | await sendEther(fantom, accounts[2], 176) 209 | // account 3 spends 1000 eth during first day 210 | await sendEther(fantom, accounts[3], 1e3) 211 | 212 | // Move to after the first day 213 | await helpers.setDate(contract.ICOStartTime+ 3600*24*2) 214 | 215 | // account 4 spends 12 eth after the first day 216 | await sendEther(fantom, accounts[4], 12) 217 | // account 5 spends 2 eth after the first day 218 | await sendEther(fantom, accounts[5], 2) 219 | 220 | actualTokenBalances = [] 221 | for (i=1; i<=5; i++) { 222 | actualTokenBalances.push((await fantom.balanceOf(accounts[i])).toNumber()); 223 | } 224 | 225 | for (i=1; i<=5; i++) { 226 | assert(expectedTokens[i] == actualTokenBalances[i], `token balance for account ${i+1} is not as expected: expected ${expectedTokens[i]} got: ${actualTokenBalances[i]}`) 227 | } 228 | }) 229 | 230 | }) 231 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/test/tests/transferMultiple.js: -------------------------------------------------------------------------------- 1 | /* 2 | * These tests were adapted from the OpenZepplin repository here: 3 | * 4 | * https://github.com/OpenZeppelin/openzeppelin-solidity 5 | */ 6 | 7 | const th = require('../utils/testHelpers'); 8 | const deployer = require('../utils/deployer.js') 9 | const assertRevert = th.assertRevert; 10 | 11 | contract('StandardToken', function (accounts) { 12 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 13 | const owner = accounts[0]; 14 | const recipient = accounts[1]; 15 | const anotherAccount = accounts[2]; 16 | const yetAnotherAccount = accounts[3]; 17 | const evenYetAnotherAccount = accounts[4]; 18 | const mintType = 1; 19 | const assignTokens = async function(token, to, amount) { 20 | await token.addToWhitelist(to, { from: owner }); 21 | await token.mintTokens(mintType, to, 100, {from: owner}); 22 | } 23 | 24 | beforeEach(async function () { 25 | let contract = await deployer.setupContract(accounts); 26 | this.token = contract.fantom; 27 | assert(owner === contract.owner); 28 | await assignTokens(this.token, owner, 100); 29 | await th.setDate(ICOEndTime + 1); 30 | await this.token.makeTradeable(); 31 | }); 32 | 33 | describe('transferMultiple', function () { 34 | describe('when the recipient is not the zero address', function () { 35 | const to = [recipient, anotherAccount, yetAnotherAccount, evenYetAnotherAccount]; 36 | const amount = [25, 25, 25, 25] 37 | 38 | describe('when the sender does not have enough balance', function () { 39 | const tooMuch = amount.map(x => x + 1) 40 | 41 | it('reverts', async function () { 42 | await assertRevert(this.token.transferMultiple(to, tooMuch, { from: owner })); 43 | }); 44 | }); 45 | 46 | describe('when the sender has enough balance', function () { 47 | 48 | it('transfers the requested amount', async function () { 49 | await this.token.transferMultiple(to, amount, { from: owner }); 50 | 51 | const senderBalance = await this.token.balanceOf(owner); 52 | assert.equal(senderBalance, 0); 53 | 54 | for(var i = 0; i < to.length; i++) { 55 | assert.equal(await this.token.balanceOf(to[i]), amount[i]); 56 | } 57 | }); 58 | 59 | it('emits a transfer event', async function () { 60 | const { logs } = await this.token.transferMultiple(to, amount, { from: owner }); 61 | 62 | assert.equal(logs.length, to.length); 63 | for(var i = 0; i < to.length; i++) { 64 | assert.equal(logs[i].event, 'Transfer'); 65 | assert.equal(logs[i].args._from, owner); 66 | assert.equal(logs[i].args._to, to[i]); 67 | assert(logs[i].args._value.eq(amount[i])); 68 | } 69 | }); 70 | }); 71 | }); 72 | 73 | describe('when the recipient is the zero address', function () { 74 | const to = [ZERO_ADDRESS, ZERO_ADDRESS]; 75 | const amount = [50, 50]; 76 | 77 | it('reverts', async function () { 78 | await assertRevert(this.token.transferMultiple(to, amount, { from: owner })); 79 | }); 80 | }); 81 | }); 82 | 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/test/utils/deployer.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | const Fantom = artifacts.require('./FantomToken.sol') 3 | const th = require('./testHelpers.js') 4 | 5 | const days = 3600*24 6 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp) 7 | 8 | module.exports.setupContract = async function(accounts) { 9 | 10 | await th.mineOne(); 11 | let owner = accounts[0] 12 | ICOStartTime = parseInt(timestamp()) + 100; 13 | ICOEndTime = ICOStartTime + 5 *days 14 | let fantom = await Fantom.new(ICOStartTime, ICOEndTime, {from: owner}) 15 | 16 | return {owner, fantom, ICOStartTime, ICOEndTime} 17 | } 18 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/test/utils/testHelpers.js: -------------------------------------------------------------------------------- 1 | var BigNumber = require("bignumber.js"); 2 | 3 | /** 4 | * Collection of Helper Function for Testing 5 | */ 6 | 7 | 8 | /** 9 | * Checks if the given string is an address 10 | * 11 | * @method isAddress 12 | * @param {String} address the given HEX adress 13 | * @return {Boolean} 14 | */ 15 | var isAddress = function (address) { 16 | if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { 17 | // check if it has the basic requirements of an address 18 | return false; 19 | } else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) { 20 | // If it's all small caps or all all caps, return true 21 | return true; 22 | } else { 23 | // Otherwise check each case 24 | return isChecksumAddress(address); 25 | } 26 | }; 27 | 28 | /** 29 | * Checks if the given string is a checksummed address 30 | * 31 | * @method isChecksumAddress 32 | * @param {String} address the given HEX adress 33 | * @return {Boolean} 34 | */ 35 | var isChecksumAddress = function (address) { 36 | // Check each case 37 | address = address.replace('0x',''); 38 | var addressHash = sha3(address.toLowerCase()); 39 | for (var i = 0; i < 40; i++ ) { 40 | // the nth letter should be uppercase if the nth digit of casemap is 1 41 | if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) { 42 | return false; 43 | } 44 | } 45 | return true; 46 | }; 47 | 48 | /* Assertion Helper Functions */ 49 | exports.assertVariable = function(contract, variable, value, message) { 50 | return variable.call() 51 | .then(function(result) { 52 | assert(result === value, message); 53 | return contract; 54 | }); 55 | }; 56 | 57 | exports.assertEthAddress = function(contract, variable, variableName) { 58 | return variable.call() 59 | .then(function(address) { 60 | assert(isAddress(address), variableName + " should be an ethereum address"); 61 | return contract; 62 | }); 63 | }; 64 | 65 | exports.assertBigNumberVariable = function(contract, variable, value, message) { 66 | return variable.call() 67 | .then(function(result) { 68 | assert(result.equals(value), message + ' ' + result.toString() + ' != ' + value); 69 | return contract; 70 | }); 71 | }; 72 | 73 | exports.assertBalanceOf = function(contract,address, value) { 74 | return contract.balanceOf.call(address) 75 | .then(function(result) { 76 | assert(result.equals(value), "balance of " + address + " should be as expected." + ' ' + result.toString() + ' != ' + value); 77 | return contract; 78 | }) 79 | }; 80 | 81 | exports.assertBigNumberArrayVariable = function(contract, variable, i, value, message) { 82 | return variable.call(i) 83 | .then(function(result) { 84 | assert(result.equals(value), message + ' ' + result.toString() + ' != ' + value); 85 | return contract; 86 | }); 87 | }; 88 | 89 | /* 90 | * Check for a throw 91 | */ 92 | exports.assertThrow = function(error) { 93 | assert.include(error.message, 'revert', 'expecting a throw/revert'); 94 | } 95 | 96 | /* 97 | * Assert that an async call threw with a revert. 98 | */ 99 | exports.assertRevert = async promise => { 100 | try { 101 | await promise; 102 | assert.fail('Expected revert not received'); 103 | } catch (error) { 104 | const revertFound = error.message.search('revert') >= 0; 105 | assert(revertFound, `Expected "revert", got ${error} instead`); 106 | } 107 | }; 108 | 109 | 110 | /* 111 | * Check the ETH the sending account gained as a result 112 | * of the transaction. This is useful for calls which 113 | * send the caller ETH, eg a 'sellTokenToEth' function. 114 | */ 115 | exports.ethReturned = async (promise , address) => { 116 | const balanceBefore = web3.eth.getBalance(address) 117 | const tx = await promise 118 | const txEthCost = web3.eth.gasPrice.times(tx.receipt.gasUsed) 119 | .times(5) // I have no idea why this 5x is required 120 | return web3.eth.getBalance(address) 121 | .minus(balanceBefore.minus(txEthCost)) 122 | } 123 | 124 | /* 125 | * Assert that `a` and `b` are equal to each other, 126 | * allowing for a `margin` of error as a percentage 127 | * of `b`. 128 | */ 129 | exports.withinPercentageOf = (a, b, percentage) => { 130 | ba = new BigNumber(a) 131 | bb = new BigNumber(b) 132 | bm = new BigNumber(percentage).dividedBy(100) 133 | return (ba.minus(bb).abs().lessThan(bb.times(bm))) 134 | } 135 | 136 | 137 | /* 138 | * TestRPC Helper functions 139 | * 140 | * These are a collection of functions to move the date and block number 141 | * of testRPC to verify contracts. 142 | */ 143 | 144 | /* Function to emulate block mining 145 | * @param - count - The number of blocks to mine 146 | */ 147 | exports.mineBlocks = function(count) { 148 | const mine = function(c, callback) { 149 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine"}) 150 | .then(() => { 151 | if (c > 1) { 152 | mine(c-1, callback); 153 | } else { 154 | callback() 155 | } 156 | }); 157 | /* 158 | request({ 159 | url: "http://localhost:8545", 160 | method: "POST", 161 | json: true, // <--Very important!!! 162 | body: { 163 | jsonrpc: "2.0", 164 | method: "evm_mine" 165 | } 166 | }, function (error, response, body){ 167 | if(c > 1) { 168 | mine(c -1, callback); 169 | } else { 170 | callback() 171 | } 172 | }); 173 | */ 174 | }; 175 | return new Promise(function(resolve, reject) { 176 | mine(count, function() { 177 | resolve(); 178 | }) 179 | }) 180 | }; 181 | 182 | exports.mineOne = function() { 183 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine"}) 184 | } 185 | 186 | /* TestRPC Snapshots 187 | * 188 | * Takes a snapshot of the current blockchain 189 | */ 190 | exports.takeSnapShot = async () => { 191 | let id = await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_snapshot"}); 192 | return id['result'] 193 | } 194 | 195 | /* 196 | * Reverts to a previous snapshot id. 197 | * @param - snapshotId - The snapshot to revert to. 198 | */ 199 | exports.revertTestRPC = function(snapshotId){ 200 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_revert", params: [snapshotId]}) 201 | } 202 | 203 | /* Sets the date of the testRPC instance to a future datetime. 204 | * @param - date - uint variable represenint a future datetime. 205 | */ 206 | exports.setDate = function(date) { 207 | // Get the current blocknumber. 208 | blockNumber = web3.eth.blockNumber; 209 | curTimeStamp = web3.eth.getBlock(blockNumber).timestamp; 210 | assert(date > curTimeStamp, "the modified date must be in the future"); 211 | // Set the new time. 212 | deltaIncrease = date - curTimeStamp; 213 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [deltaIncrease]}) 214 | // mine a block to ensure we get a new timestamp 215 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine"}) 216 | }; 217 | 218 | exports.sleep = async (sleeptime) => { 219 | return new Promise((resolve) => { 220 | setTimeout(resolve, sleeptime) 221 | }) 222 | } 223 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-one/truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | }, 9 | solc: { 10 | optimizer: { 11 | enabled: true, 12 | runs: 200 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/README.md: -------------------------------------------------------------------------------- 1 | # Fantom Token Review v2 2 | 3 | This directory contains the testing suite used in the security review. In order 4 | to run the tests the requisite node modules need to be installed. This can be 5 | achieved via: 6 | 7 | ``` 8 | $ npm install 9 | ``` 10 | 11 | A ganache-like client (`testrpc` and `geth --dev` are alternative options) need to be running. These tests require a large number off accounts with a large amount of ether. Example cli options are given in `largeTestAccounts.sh` 12 | 13 | ``` 14 | $ ./largeTestAccounts 15 | ``` 16 | 17 | The tests can then be executed via 18 | 19 | ``` 20 | truffle test 21 | ``` 22 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/contracts/FantomToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // 5 | // Fantom Foundation FTM token public sale contract 6 | // 7 | // For details, please visit: http://fantom.foundation 8 | // 9 | // 10 | // written by Alex Kampa - ak@sikoba.com 11 | // 12 | // ---------------------------------------------------------------------------- 13 | 14 | 15 | // ---------------------------------------------------------------------------- 16 | // 17 | // SafeMath 18 | // 19 | // ---------------------------------------------------------------------------- 20 | 21 | library SafeMath { 22 | 23 | function add(uint a, uint b) internal pure returns (uint c) { 24 | c = a + b; 25 | require(c >= a); 26 | } 27 | 28 | function sub(uint a, uint b) internal pure returns (uint c) { 29 | require(b <= a); 30 | c = a - b; 31 | } 32 | 33 | function mul(uint a, uint b) internal pure returns (uint c) { 34 | c = a * b; 35 | require(a == 0 || c / a == b); 36 | } 37 | 38 | } 39 | 40 | // ---------------------------------------------------------------------------- 41 | // 42 | // Owned 43 | // 44 | // ---------------------------------------------------------------------------- 45 | 46 | contract Owned { 47 | 48 | address public owner; 49 | address public newOwner; 50 | 51 | mapping(address => bool) public isAdmin; 52 | 53 | event OwnershipTransferProposed(address indexed _from, address indexed _to); 54 | event OwnershipTransferred(address indexed _from, address indexed _to); 55 | event AdminChange(address indexed _admin, bool _status); 56 | 57 | modifier onlyOwner {require(msg.sender == owner); _;} 58 | modifier onlyAdmin {require(isAdmin[msg.sender]); _;} 59 | 60 | constructor() public { 61 | owner = msg.sender; 62 | isAdmin[owner] = true; 63 | } 64 | 65 | function transferOwnership(address _newOwner) public onlyOwner { 66 | require(_newOwner != address(0x0)); 67 | emit OwnershipTransferProposed(owner, _newOwner); 68 | newOwner = _newOwner; 69 | } 70 | 71 | function acceptOwnership() public { 72 | require(msg.sender == newOwner); 73 | emit OwnershipTransferred(owner, newOwner); 74 | owner = newOwner; 75 | } 76 | 77 | function addAdmin(address _a) public onlyOwner { 78 | require(isAdmin[_a] == false); 79 | isAdmin[_a] = true; 80 | emit AdminChange(_a, true); 81 | } 82 | 83 | function removeAdmin(address _a) public onlyOwner { 84 | require(isAdmin[_a] == true); 85 | isAdmin[_a] = false; 86 | emit AdminChange(_a, false); 87 | } 88 | 89 | } 90 | 91 | 92 | // ---------------------------------------------------------------------------- 93 | // 94 | // Wallet 95 | // 96 | // ---------------------------------------------------------------------------- 97 | 98 | contract Wallet is Owned { 99 | 100 | address public wallet; 101 | 102 | event WalletUpdated(address newWallet); 103 | 104 | constructor() public { 105 | wallet = owner; 106 | } 107 | 108 | function setWallet(address _wallet) public onlyOwner { 109 | require(_wallet != address(0x0)); 110 | wallet = _wallet; 111 | emit WalletUpdated(_wallet); 112 | } 113 | 114 | } 115 | 116 | 117 | // ---------------------------------------------------------------------------- 118 | // 119 | // ERC20Interface 120 | // 121 | // ---------------------------------------------------------------------------- 122 | 123 | contract ERC20Interface { 124 | 125 | event Transfer(address indexed _from, address indexed _to, uint _value); 126 | event Approval(address indexed _owner, address indexed _spender, uint _value); 127 | 128 | function totalSupply() public view returns (uint); 129 | function balanceOf(address _owner) public view returns (uint balance); 130 | function transfer(address _to, uint _value) public returns (bool success); 131 | function transferFrom(address _from, address _to, uint _value) public returns (bool success); 132 | function approve(address _spender, uint _value) public returns (bool success); 133 | function allowance(address _owner, address _spender) public view returns (uint remaining); 134 | 135 | } 136 | 137 | 138 | // ---------------------------------------------------------------------------- 139 | // 140 | // ERC Token Standard #20 141 | // 142 | // ---------------------------------------------------------------------------- 143 | 144 | contract ERC20Token is ERC20Interface, Owned { 145 | 146 | using SafeMath for uint; 147 | 148 | uint public tokensIssuedTotal; 149 | mapping(address => uint) balances; 150 | mapping(address => mapping (address => uint)) allowed; 151 | 152 | function totalSupply() public view returns (uint) { 153 | return tokensIssuedTotal; 154 | } 155 | // Includes BOTH locked AND unlocked tokens 156 | 157 | function balanceOf(address _owner) public view returns (uint) { 158 | return balances[_owner]; 159 | } 160 | 161 | function transfer(address _to, uint _amount) public returns (bool) { 162 | require(_to != 0x0); 163 | balances[msg.sender] = balances[msg.sender].sub(_amount); 164 | balances[_to] = balances[_to].add(_amount); 165 | emit Transfer(msg.sender, _to, _amount); 166 | return true; 167 | } 168 | 169 | function approve(address _spender, uint _amount) public returns (bool) { 170 | allowed[msg.sender][_spender] = _amount; 171 | emit Approval(msg.sender, _spender, _amount); 172 | return true; 173 | } 174 | 175 | function transferFrom(address _from, address _to, uint _amount) public returns (bool) { 176 | require(_to != 0x0); 177 | balances[_from] = balances[_from].sub(_amount); 178 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount); 179 | balances[_to] = balances[_to].add(_amount); 180 | emit Transfer(_from, _to, _amount); 181 | return true; 182 | } 183 | 184 | function allowance(address _owner, address _spender) public view returns (uint) { 185 | return allowed[_owner][_spender]; 186 | } 187 | 188 | } 189 | 190 | 191 | // ---------------------------------------------------------------------------- 192 | // 193 | // LockSlots 194 | // 195 | // ---------------------------------------------------------------------------- 196 | 197 | contract LockSlots is ERC20Token { 198 | 199 | using SafeMath for uint; 200 | 201 | uint public constant LOCK_SLOTS = 5; 202 | mapping(address => uint[LOCK_SLOTS]) public lockTerm; 203 | mapping(address => uint[LOCK_SLOTS]) public lockAmnt; 204 | mapping(address => bool) public mayHaveLockedTokens; 205 | 206 | event RegisteredLockedTokens(address indexed account, uint indexed idx, uint tokens, uint term); 207 | 208 | function registerLockedTokens(address _account, uint _tokens, uint _term) internal returns (uint idx) { 209 | require(_term > now, "lock term must be in the future"); 210 | 211 | // find a slot (clean up while doing this) 212 | // use either the existing slot with the exact same term, 213 | // of which there can be at most one, or the first empty slot 214 | idx = 9999; 215 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 216 | uint[LOCK_SLOTS] storage amnt = lockAmnt[_account]; 217 | for (uint i; i < LOCK_SLOTS; i++) { 218 | if (term[i] < now) { 219 | term[i] = 0; 220 | amnt[i] = 0; 221 | if (idx == 9999) idx = i; 222 | } 223 | if (term[i] == _term) idx = i; 224 | } 225 | 226 | // fail if no slot was found 227 | require(idx != 9999, "registerLockedTokens: no available slot found"); 228 | 229 | // register locked tokens 230 | if (term[idx] == 0) term[idx] = _term; 231 | amnt[idx] = amnt[idx].add(_tokens); 232 | mayHaveLockedTokens[_account] = true; 233 | emit RegisteredLockedTokens(_account, idx, _tokens, _term); 234 | } 235 | 236 | // public view functions 237 | 238 | function lockedTokens(address _account) public view returns (uint) { 239 | if (!mayHaveLockedTokens[_account]) return 0; 240 | return pNumberOfLockedTokens(_account); 241 | } 242 | 243 | function unlockedTokens(address _account) public view returns (uint) { 244 | return balances[_account].sub(lockedTokens(_account)); 245 | } 246 | 247 | function isAvailableLockSlot(address _account, uint _term) public view returns (bool) { 248 | if (!mayHaveLockedTokens[_account]) return true; 249 | if (_term < now) return true; 250 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 251 | for (uint i; i < LOCK_SLOTS; i++) { 252 | if (term[i] < now || term[i] == _term) return true; 253 | } 254 | return false; 255 | } 256 | 257 | // internal and private functions 258 | 259 | function unlockedTokensInternal(address _account) internal returns (uint) { 260 | // updates mayHaveLockedTokens if necessary 261 | if (!mayHaveLockedTokens[_account]) return balances[_account]; 262 | uint locked = pNumberOfLockedTokens(_account); 263 | if (locked == 0) mayHaveLockedTokens[_account] = false; 264 | return balances[_account].sub(locked); 265 | } 266 | 267 | function pNumberOfLockedTokens(address _account) private view returns (uint locked) { 268 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 269 | uint[LOCK_SLOTS] storage amnt = lockAmnt[_account]; 270 | for (uint i; i < LOCK_SLOTS; i++) { 271 | if (term[i] >= now) locked = locked.add(amnt[i]); 272 | } 273 | } 274 | 275 | } 276 | 277 | 278 | // ---------------------------------------------------------------------------- 279 | // 280 | // FantomIcoDates 281 | // 282 | // ---------------------------------------------------------------------------- 283 | 284 | contract FantomIcoDates is Owned { 285 | 286 | uint public dateMainStart = 1529053200; // 15-JUN-2018 09:00 UTC 287 | uint public dateMainEnd = 1529658000; // 22-JUN-2018 09:00 UTC 288 | 289 | uint public constant DATE_LIMIT = 1529658000 + 180 days; 290 | 291 | event IcoDateUpdated(uint id, uint unixts); 292 | 293 | // check dates 294 | event debug(uint start, uint end); 295 | 296 | modifier checkDateOrder { 297 | _ ; 298 | require ( dateMainStart < dateMainEnd ) ; 299 | // Commented for testing date changes 300 | // require ( dateMainEnd < DATE_LIMIT ) ; 301 | } 302 | 303 | constructor(uint _dateMainStart, uint _dateMainEnd) public checkDateOrder() { 304 | dateMainStart = _dateMainStart; 305 | dateMainEnd = _dateMainEnd; 306 | require(now < dateMainStart); 307 | } 308 | 309 | // set ico dates 310 | 311 | function setDateMainStart(uint _unixts) public onlyOwner checkDateOrder { 312 | require(now < _unixts && now < dateMainStart); 313 | dateMainStart = _unixts; 314 | emit IcoDateUpdated(1, _unixts); 315 | } 316 | 317 | function setDateMainEnd(uint _unixts) public onlyOwner checkDateOrder { 318 | require(now < _unixts && now < dateMainEnd); 319 | dateMainEnd = _unixts; 320 | emit IcoDateUpdated(2, _unixts); 321 | } 322 | 323 | // where are we? 324 | 325 | function isMainFirstDay() public view returns (bool) { 326 | if (now > dateMainStart && now <= dateMainStart + 1 days) return true; 327 | return false; 328 | } 329 | 330 | function isMain() public view returns (bool) { 331 | if (now > dateMainStart && now < dateMainEnd) return true; 332 | return false; 333 | } 334 | 335 | } 336 | 337 | // ---------------------------------------------------------------------------- 338 | // 339 | // Fantom public token sale 340 | // 341 | // ---------------------------------------------------------------------------- 342 | 343 | contract FantomToken is ERC20Token, Wallet, LockSlots, FantomIcoDates { 344 | 345 | // Utility variable 346 | 347 | uint constant E18 = 10**18; 348 | 349 | // Basic token data 350 | 351 | string public constant name = "Fantom Token"; 352 | string public constant symbol = "FTM"; 353 | uint8 public constant decimals = 18; 354 | 355 | // Token number of possible tokens in existance 356 | 357 | uint public constant MAX_TOTAL_TOKEN_SUPPLY = 3175000000 * E18; 358 | 359 | 360 | // crowdsale parameters 361 | 362 | uint public tokensPerEth = 15000; 363 | 364 | uint public constant MINIMUM_CONTRIBUTION = 0.2 ether; 365 | uint public constant MAXIMUM_FIRST_DAY_CONTRIBUTION = 100 ether; 366 | 367 | uint public constant TOKEN_MAIN_CAP = 50000000 * E18; 368 | 369 | bool public tokensTradeable; 370 | 371 | // whitelisting 372 | 373 | mapping(address => bool) public whitelist; 374 | uint public numberWhitelisted; 375 | 376 | // track main sale 377 | 378 | uint public tokensMain; 379 | mapping(address => uint) public balancesMain; 380 | 381 | uint public totalEthContributed; 382 | mapping(address => uint) public ethContributed; 383 | 384 | // tracking tokens minted 385 | 386 | uint public tokensMinted; 387 | mapping(address => uint) public balancesMinted; 388 | mapping(address => mapping(uint => uint)) public balancesMintedByType; 389 | 390 | // migration variable 391 | 392 | bool public isMigrationPhaseOpen; 393 | 394 | // Events --------------------------------------------- 395 | 396 | event UpdatedTokensPerEth(uint tokensPerEth); 397 | event Whitelisted(address indexed account, uint countWhitelisted); 398 | event TokensMinted(uint indexed mintType, address indexed account, uint tokens, uint term); 399 | event RegisterContribution(address indexed account, uint tokensIssued, uint ethContributed, uint ethReturned); 400 | event TokenExchangeRequested(address indexed account, uint tokens); 401 | 402 | // Basic Functions ------------------------------------ 403 | 404 | constructor(uint _dateMainStart, uint _dateMainEnd) public 405 | FantomIcoDates(_dateMainStart, _dateMainEnd) { } 406 | 407 | function () public payable { 408 | buyTokens(); 409 | } 410 | 411 | // Information functions 412 | 413 | 414 | function availableToMint() public view returns (uint) { 415 | return MAX_TOTAL_TOKEN_SUPPLY.sub(TOKEN_MAIN_CAP).sub(tokensMinted); 416 | } 417 | 418 | function firstDayTokenLimit() public view returns (uint) { 419 | return ethToTokens(MAXIMUM_FIRST_DAY_CONTRIBUTION); 420 | } 421 | 422 | function ethToTokens(uint _eth) public view returns (uint tokens) { 423 | tokens = _eth.mul(tokensPerEth); 424 | } 425 | 426 | function tokensToEth(uint _tokens) public view returns (uint eth) { 427 | eth = _tokens / tokensPerEth; 428 | } 429 | 430 | // Admin functions 431 | 432 | function addToWhitelist(address _account) public onlyAdmin { 433 | pWhitelist(_account); 434 | } 435 | 436 | function addToWhitelistMultiple(address[] _addresses) public onlyAdmin { 437 | for (uint i; i < _addresses.length; i++) { 438 | pWhitelist(_addresses[i]); 439 | } 440 | } 441 | 442 | function pWhitelist(address _account) internal { 443 | if (whitelist[_account]) return; 444 | whitelist[_account] = true; 445 | numberWhitelisted = numberWhitelisted.add(1); 446 | emit Whitelisted(_account, numberWhitelisted); 447 | } 448 | 449 | // Owner functions ------------------------------------ 450 | 451 | function updateTokensPerEth(uint _tokens_per_eth) public onlyOwner { 452 | require(now < dateMainStart); 453 | tokensPerEth = _tokens_per_eth; 454 | emit UpdatedTokensPerEth(tokensPerEth); 455 | } 456 | 457 | // Only owner can make tokens tradable at any time, or if the date is 458 | // greater than the end of the mainsale date plus 20 weeks, allow 459 | // any caller to make tokensTradeable. 460 | 461 | function makeTradeable() public { 462 | require(msg.sender == owner || now > dateMainEnd + 20 weeks); 463 | tokensTradeable = true; 464 | } 465 | 466 | function openMigrationPhase() public onlyOwner { 467 | require(now > dateMainEnd); 468 | isMigrationPhaseOpen = true; 469 | } 470 | 471 | // Token minting -------------------------------------- 472 | 473 | function mintTokens(uint _mint_type, address _account, uint _tokens) public onlyOwner { 474 | pMintTokens(_mint_type, _account, _tokens, 0); 475 | } 476 | 477 | function mintTokensMultiple(uint _mint_type, address[] _accounts, uint[] _tokens) public onlyOwner { 478 | require(_accounts.length == _tokens.length); 479 | for (uint i; i < _accounts.length; i++) { 480 | pMintTokens(_mint_type, _accounts[i], _tokens[i], 0); 481 | } 482 | } 483 | 484 | function mintTokensLocked(uint _mint_type, address _account, uint _tokens, uint _term) public onlyOwner { 485 | pMintTokens(_mint_type, _account, _tokens, _term); 486 | } 487 | 488 | function mintTokensLockedMultiple(uint _mint_type, address[] _accounts, uint[] _tokens, uint[] _terms) public onlyOwner { 489 | require(_accounts.length == _tokens.length); 490 | require(_accounts.length == _terms.length); 491 | for (uint i; i < _accounts.length; i++) { 492 | pMintTokens(_mint_type, _accounts[i], _tokens[i], _terms[i]); 493 | } 494 | } 495 | 496 | function pMintTokens(uint _mint_type, address _account, uint _tokens, uint _term) private { 497 | require(whitelist[_account]); 498 | require(_account != 0x0); 499 | require(_tokens > 0); 500 | require(_tokens <= availableToMint(), "not enough tokens available to mint"); 501 | require(_term == 0 || _term > now, "either without lock term, or lock term must be in the future"); 502 | 503 | // register locked tokens (will throw if no slot is found) 504 | if (_term > 0) registerLockedTokens(_account, _tokens, _term); 505 | 506 | // update 507 | balances[_account] = balances[_account].add(_tokens); 508 | balancesMinted[_account] = balancesMinted[_account].add(_tokens); 509 | balancesMintedByType[_account][_mint_type] = balancesMintedByType[_account][_mint_type].add(_tokens); 510 | tokensMinted = tokensMinted.add(_tokens); 511 | tokensIssuedTotal = tokensIssuedTotal.add(_tokens); 512 | 513 | // log event 514 | emit Transfer(0x0, _account, _tokens); 515 | emit TokensMinted(_mint_type, _account, _tokens, _term); 516 | } 517 | 518 | // Main sale ------------------------------------------ 519 | 520 | function buyTokens() private { 521 | 522 | require(isMain()); 523 | require(msg.value >= MINIMUM_CONTRIBUTION); 524 | require(whitelist[msg.sender]); 525 | 526 | uint tokens_available = TOKEN_MAIN_CAP.sub(tokensMain); 527 | 528 | // adjust tokens_available on first day, if necessary 529 | if (isMainFirstDay()) { 530 | uint tokens_available_first_day = firstDayTokenLimit().sub(balancesMain[msg.sender]); 531 | if (tokens_available_first_day < tokens_available) { 532 | tokens_available = tokens_available_first_day; 533 | } 534 | } 535 | 536 | require (tokens_available > 0); 537 | 538 | uint tokens_requested = ethToTokens(msg.value); 539 | uint tokens_issued = tokens_requested; 540 | 541 | uint eth_contributed = msg.value; 542 | uint eth_returned; 543 | 544 | if (tokens_requested > tokens_available) { 545 | tokens_issued = tokens_available; 546 | eth_returned = tokensToEth(tokens_requested.sub(tokens_available)); 547 | eth_contributed = msg.value.sub(eth_returned); 548 | } 549 | 550 | balances[msg.sender] = balances[msg.sender].add(tokens_issued); 551 | balancesMain[msg.sender] = balancesMain[msg.sender].add(tokens_issued); 552 | tokensMain = tokensMain.add(tokens_issued); 553 | tokensIssuedTotal = tokensIssuedTotal.add(tokens_issued); 554 | 555 | ethContributed[msg.sender] = ethContributed[msg.sender].add(eth_contributed); 556 | totalEthContributed = totalEthContributed.add(eth_contributed); 557 | 558 | // ether transfers 559 | if (eth_returned > 0) msg.sender.transfer(eth_returned); 560 | wallet.transfer(eth_contributed); 561 | 562 | // log 563 | emit Transfer(0x0, msg.sender, tokens_issued); 564 | emit RegisterContribution(msg.sender, tokens_issued, eth_contributed, eth_returned); 565 | } 566 | 567 | // Token exchange / migration to new platform --------- 568 | 569 | function requestTokenExchangeMax() public { 570 | requestTokenExchange(unlockedTokensInternal(msg.sender)); 571 | } 572 | 573 | function requestTokenExchange(uint _tokens) public { 574 | require(isMigrationPhaseOpen); 575 | require(_tokens > 0 && _tokens <= unlockedTokensInternal(msg.sender)); 576 | balances[msg.sender] = balances[msg.sender].sub(_tokens); 577 | tokensIssuedTotal = tokensIssuedTotal.sub(_tokens); 578 | emit Transfer(msg.sender, 0x0, _tokens); 579 | emit TokenExchangeRequested(msg.sender, _tokens); 580 | } 581 | 582 | // ERC20 functions ------------------- 583 | 584 | /* Transfer out any accidentally sent ERC20 tokens */ 585 | 586 | function transferAnyERC20Token(address _token_address, uint _amount) public onlyOwner returns (bool success) { 587 | return ERC20Interface(_token_address).transfer(owner, _amount); 588 | } 589 | 590 | /* Override "transfer" */ 591 | 592 | function transfer(address _to, uint _amount) public returns (bool success) { 593 | require(tokensTradeable); 594 | require(_amount <= unlockedTokensInternal(msg.sender)); 595 | return super.transfer(_to, _amount); 596 | } 597 | 598 | /* Override "transferFrom" */ 599 | 600 | function transferFrom(address _from, address _to, uint _amount) public returns (bool success) { 601 | require(tokensTradeable); 602 | require(_amount <= unlockedTokensInternal(_from)); 603 | return super.transferFrom(_from, _to, _amount); 604 | } 605 | 606 | /* Multiple token transfers from one address to save gas */ 607 | 608 | function transferMultiple(address[] _addresses, uint[] _amounts) external { 609 | require(_addresses.length <= 100); 610 | require(_addresses.length == _amounts.length); 611 | 612 | // do the transfers 613 | for (uint j; j < _addresses.length; j++) { 614 | transfer(_addresses[j], _amounts[j]); 615 | } 616 | 617 | } 618 | 619 | } 620 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/largeTestAccounts.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | ganache-cli --accounts 50 --defaultBalanceEther 50000000000 4 | 5 | 6 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/migrations/empty_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantom-foundation/tokensale/eb260602c431f6a522bbfc39100dbef9ac8a320a/audits/testsuite/fantom-audit-truffle-two/migrations/empty_file -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "bignumber.js": "5.0.0", 11 | "ganache-cli": "^6.1.2", 12 | "solc": "^0.4.24", 13 | "solc-js": "^0.4.20-browser.1", 14 | "truffle-artifactor": "^3.0.4" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "author": "", 21 | "license": "ISC" 22 | } 23 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/tests/erc20.js: -------------------------------------------------------------------------------- 1 | /* 2 | * These tests were adapted from the OpenZepplin repository here: 3 | * 4 | * https://github.com/OpenZeppelin/openzeppelin-solidity 5 | */ 6 | 7 | const th = require('../utils/testHelpers'); 8 | const deployer = require('../utils/deployer.js') 9 | const assertRevert = th.assertRevert; 10 | 11 | contract('StandardToken', function (accounts) { 12 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 13 | const owner = accounts[0]; 14 | const recipient = accounts[1]; 15 | const anotherAccount = accounts[2]; 16 | const mintType = 1; 17 | const assignTokens = async function(token, to, amount) { 18 | await token.addToWhitelist(to, { from: owner }); 19 | await token.mintTokens(mintType, to, 100, {from: owner}); 20 | } 21 | 22 | beforeEach(async function () { 23 | let contract = await deployer.setupContract(accounts); 24 | this.token = contract.fantom; 25 | assert(owner === contract.owner); 26 | await assignTokens(this.token, owner, 100); 27 | await th.setDate(ICOEndTime + 1); 28 | await this.token.makeTradeable(); 29 | }); 30 | 31 | describe('total supply', function () { 32 | it('returns the total amount of tokens immediately after deployment', async function () { 33 | const totalSupply = await this.token.totalSupply(); 34 | 35 | assert.equal(totalSupply, 100); 36 | }); 37 | }); 38 | 39 | describe('balanceOf', function () { 40 | describe('when the requested account has no tokens', function () { 41 | it('returns zero', async function () { 42 | const balance = await this.token.balanceOf(anotherAccount); 43 | 44 | assert.equal(balance, 0); 45 | }); 46 | }); 47 | 48 | describe('when the requested account has some tokens', function () { 49 | it('returns the total amount of tokens', async function () { 50 | const balance = await this.token.balanceOf(owner); 51 | 52 | assert.equal(balance, 100); 53 | }); 54 | }); 55 | }); 56 | 57 | describe('transfer', function () { 58 | describe('when the recipient is not the zero address', function () { 59 | const to = recipient; 60 | 61 | describe('when the sender does not have enough balance', function () { 62 | const amount = 101; 63 | 64 | it('reverts', async function () { 65 | await assertRevert(this.token.transfer(to, amount, { from: owner })); 66 | }); 67 | }); 68 | 69 | describe('when the sender has enough balance', function () { 70 | const amount = 100; 71 | 72 | it('transfers the requested amount', async function () { 73 | await this.token.transfer(to, amount, { from: owner }); 74 | 75 | const senderBalance = await this.token.balanceOf(owner); 76 | assert.equal(senderBalance, 0); 77 | 78 | const recipientBalance = await this.token.balanceOf(to); 79 | assert.equal(recipientBalance, amount); 80 | }); 81 | 82 | it('emits a transfer event', async function () { 83 | const { logs } = await this.token.transfer(to, amount, { from: owner }); 84 | 85 | assert.equal(logs.length, 1); 86 | assert.equal(logs[0].event, 'Transfer'); 87 | assert.equal(logs[0].args._from, owner); 88 | assert.equal(logs[0].args._to, to); 89 | assert(logs[0].args._value.eq(amount)); 90 | }); 91 | }); 92 | }); 93 | 94 | describe('when the recipient is the zero address', function () { 95 | const to = ZERO_ADDRESS; 96 | 97 | it('reverts', async function () { 98 | await assertRevert(this.token.transfer(to, 100, { from: owner })); 99 | }); 100 | }); 101 | }); 102 | 103 | describe('approve', function () { 104 | describe('when the spender is not the zero address', function () { 105 | const spender = recipient; 106 | 107 | describe('when the sender has enough balance', function () { 108 | const amount = 100; 109 | 110 | it('emits an approval event', async function () { 111 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 112 | 113 | assert.equal(logs.length, 1); 114 | assert.equal(logs[0].event, 'Approval'); 115 | assert.equal(logs[0].args._owner, owner); 116 | assert.equal(logs[0].args._spender, spender); 117 | assert(logs[0].args._value.eq(amount)); 118 | }); 119 | 120 | describe('when there was no approved amount before', function () { 121 | it('approves the requested amount', async function () { 122 | await this.token.approve(spender, amount, { from: owner }); 123 | 124 | const allowance = await this.token.allowance(owner, spender); 125 | assert.equal(allowance, amount); 126 | }); 127 | }); 128 | 129 | describe('when the spender had an approved amount', function () { 130 | beforeEach(async function () { 131 | await this.token.approve(spender, 1, { from: owner }); 132 | }); 133 | 134 | it('approves the requested amount and replaces the previous one', async function () { 135 | await this.token.approve(spender, amount, { from: owner }); 136 | 137 | const allowance = await this.token.allowance(owner, spender); 138 | assert.equal(allowance, amount); 139 | }); 140 | }); 141 | }); 142 | 143 | describe('when the sender does not have enough balance', function () { 144 | const amount = 101; 145 | 146 | it('emits an approval event', async function () { 147 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 148 | 149 | assert.equal(logs.length, 1); 150 | assert.equal(logs[0].event, 'Approval'); 151 | assert.equal(logs[0].args._owner, owner); 152 | assert.equal(logs[0].args._spender, spender); 153 | assert(logs[0].args._value.eq(amount)); 154 | }); 155 | 156 | describe('when there was no approved amount before', function () { 157 | it('approves the requested amount', async function () { 158 | await this.token.approve(spender, amount, { from: owner }); 159 | 160 | const allowance = await this.token.allowance(owner, spender); 161 | assert.equal(allowance, amount); 162 | }); 163 | }); 164 | 165 | describe('when the spender had an approved amount', function () { 166 | beforeEach(async function () { 167 | await this.token.approve(spender, 1, { from: owner }); 168 | }); 169 | 170 | it('approves the requested amount and replaces the previous one', async function () { 171 | await this.token.approve(spender, amount, { from: owner }); 172 | 173 | const allowance = await this.token.allowance(owner, spender); 174 | assert.equal(allowance, amount); 175 | }); 176 | }); 177 | }); 178 | }); 179 | 180 | describe('when the spender is the zero address', function () { 181 | const amount = 100; 182 | const spender = ZERO_ADDRESS; 183 | 184 | it('approves the requested amount', async function () { 185 | await this.token.approve(spender, amount, { from: owner }); 186 | 187 | const allowance = await this.token.allowance(owner, spender); 188 | assert.equal(allowance, amount); 189 | }); 190 | 191 | it('emits an approval event', async function () { 192 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 193 | 194 | assert.equal(logs.length, 1); 195 | assert.equal(logs[0].event, 'Approval'); 196 | assert.equal(logs[0].args._owner, owner); 197 | assert.equal(logs[0].args._spender, spender); 198 | assert(logs[0].args._value.eq(amount)); 199 | }); 200 | }); 201 | }); 202 | 203 | describe('transfer from', function () { 204 | const spender = recipient; 205 | 206 | describe('when the recipient is not the zero address', function () { 207 | const to = anotherAccount; 208 | 209 | describe('when the spender has enough approved balance', function () { 210 | beforeEach(async function () { 211 | await this.token.approve(spender, 100, { from: owner }); 212 | }); 213 | 214 | describe('when the owner has enough balance', function () { 215 | const amount = 100; 216 | 217 | it('transfers the requested amount', async function () { 218 | await this.token.transferFrom(owner, to, amount, { from: spender }); 219 | 220 | const senderBalance = await this.token.balanceOf(owner); 221 | assert.equal(senderBalance, 0); 222 | 223 | const recipientBalance = await this.token.balanceOf(to); 224 | assert.equal(recipientBalance, amount); 225 | }); 226 | 227 | it('decreases the spender allowance', async function () { 228 | await this.token.transferFrom(owner, to, amount, { from: spender }); 229 | 230 | const allowance = await this.token.allowance(owner, spender); 231 | assert(allowance.eq(0)); 232 | }); 233 | 234 | it('emits a transfer event', async function () { 235 | const { logs } = await this.token.transferFrom(owner, to, amount, { from: spender }); 236 | 237 | assert.equal(logs.length, 1); 238 | assert.equal(logs[0].event, 'Transfer'); 239 | assert.equal(logs[0].args._from, owner); 240 | assert.equal(logs[0].args._to, to); 241 | assert(logs[0].args._value.eq(amount)); 242 | }); 243 | }); 244 | 245 | describe('when the owner does not have enough balance', function () { 246 | const amount = 101; 247 | 248 | it('reverts', async function () { 249 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 250 | }); 251 | }); 252 | }); 253 | 254 | describe('when the spender does not have enough approved balance', function () { 255 | beforeEach(async function () { 256 | await this.token.approve(spender, 99, { from: owner }); 257 | }); 258 | 259 | describe('when the owner has enough balance', function () { 260 | const amount = 100; 261 | 262 | it('reverts', async function () { 263 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 264 | }); 265 | }); 266 | 267 | describe('when the owner does not have enough balance', function () { 268 | const amount = 101; 269 | 270 | it('reverts', async function () { 271 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 272 | }); 273 | }); 274 | }); 275 | }); 276 | 277 | describe('when the recipient is the zero address', function () { 278 | const amount = 100; 279 | const to = ZERO_ADDRESS; 280 | 281 | beforeEach(async function () { 282 | await this.token.approve(spender, amount, { from: owner }); 283 | }); 284 | 285 | it('reverts', async function () { 286 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 287 | }); 288 | }); 289 | }); 290 | }); 291 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/tests/fantom.js: -------------------------------------------------------------------------------- 1 | const deployer = require('../utils/deployer.js') 2 | const helpers = require('../utils/testHelpers.js') 3 | const BigNumber = require('bignumber.js') 4 | 5 | const Fantom = artifacts.require('./FantomToken.sol') 6 | 7 | /* 8 | * Return the amount of seconds in x days 9 | */ 10 | const days = (x) => x * 60 * 60 * 24 11 | assert(days(2) === 172800, 'days() is wrong') 12 | 13 | const hours = (x) => x * 60 * 60 14 | assert(hours(2) == 7200, `hours() is wrong`) 15 | 16 | const minutes = (x) => x * 60 17 | 18 | const dateBySeconds = (x) => new BigNumber(Math.floor(x/1000)) 19 | assert(dateBySeconds(new Date(Date.UTC(2018, 00, 01, 10, 10, 10))).cmp((new BigNumber(1514801410))) == 0, 'dateBySeconds() is wrong') 20 | 21 | /* 22 | * Return the timestamp of the current block 23 | */ 24 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp) 25 | 26 | contract('LockSlots', (accounts) => { 27 | 28 | it('[isAvailableLockSlot] should return true for account no locked slot', async () => { 29 | const rig = await deployer.setupContract(accounts) 30 | const holder = accounts[2] 31 | 32 | let slotStatus = await rig.fantom.isAvailableLockSlot(holder, 33 | dateBySeconds(new Date(2018, 04, 23, 23, 01, 01))) 34 | 35 | assert(slotStatus == true, `Lock Status, expected true; got ${slotStatus}`) 36 | 37 | // Check in the past 38 | slotStatus = await rig.fantom.isAvailableLockSlot(holder, 39 | dateBySeconds(new Date(2017, 04, 23, 23, 01, 01))) 40 | 41 | assert(slotStatus == true, `Lock Status, expected true; got ${slotStatus}`) 42 | }) 43 | 44 | it('[mintTokensLocked] should lock correct number of tokens', async () => { 45 | const rig = await deployer.setupContract(accounts) 46 | const owner = rig.owner 47 | const fantom = rig.fantom 48 | const lockedHolder = accounts[2] 49 | 50 | // whitelist the account 51 | await fantom.addToWhitelist(lockedHolder, {from: owner}) 52 | 53 | assert(true == await fantom.whitelist.call(lockedHolder), 'Whitelist status incorrect') 54 | 55 | // mint locked tokens for account 56 | let dateToLock = rig.ICOStartTime + days(5) 57 | await fantom.mintTokensLocked(1, lockedHolder, 10, dateToLock, {from: owner}) 58 | 59 | // check the number of locked tokens 60 | let lockedTokens = await fantom.lockedTokens(lockedHolder) 61 | 62 | assert(lockedTokens.cmp(new BigNumber(10)) == 0, `Number of minted tokens locked; expected 10; Got: ${lockedTokens}`) 63 | }) 64 | 65 | it('[mintTokensLockedMultiple] mint the correct amount of locked tokens', async () => { 66 | const rig = await deployer.setupContract(accounts) 67 | const owner = rig.owner 68 | const fantom = rig.fantom 69 | const startDate = rig.ICOStartTime 70 | const lockedHolder = accounts[2] 71 | 72 | // whitelist the account 73 | await fantom.addToWhitelist(lockedHolder, {from: owner}) 74 | 75 | let initialDate = new BigNumber(startDate + days(1)) 76 | 77 | tokensToMint = [5,4,3,2,1] 78 | terms = [ 79 | initialDate.plus(days(1)), 80 | initialDate.plus(days(2)), 81 | initialDate.plus(days(3)), 82 | initialDate.plus(days(4)), 83 | initialDate.plus(days(5)) 84 | ] 85 | 86 | accounts = [ 87 | lockedHolder, 88 | lockedHolder, 89 | lockedHolder, 90 | lockedHolder, 91 | lockedHolder 92 | ] 93 | 94 | await fantom.mintTokensLockedMultiple(1, accounts, tokensToMint, terms, {from: owner}) 95 | 96 | let lockedTokens = await fantom.lockedTokens(lockedHolder) 97 | 98 | assert(lockedTokens.cmp(new BigNumber(15)) == 0, `Number of minted tokens locked; expected 15; Got: ${lockedTokens}`) 99 | }) 100 | 101 | it('[mintTokensLockedMultiple] number of lockslots fill correctly', async () => { 102 | const rig = await deployer.setupContract(accounts) 103 | const owner = rig.owner 104 | const fantom = rig.fantom 105 | const startDate = rig.ICOStartTime 106 | const lockedHolder = accounts[2] 107 | 108 | // whitelist the account 109 | await fantom.addToWhitelist(lockedHolder, {from: owner}) 110 | 111 | let initialDate = new BigNumber(startDate + days(1)) 112 | tokensToMint = [5,4,3,2,1] 113 | terms = [ 114 | initialDate.plus(days(1)), 115 | initialDate.plus(days(2)), 116 | initialDate.plus(days(3)), 117 | initialDate.plus(days(4)), 118 | initialDate.plus(days(5)) 119 | ] 120 | 121 | accounts = [ 122 | lockedHolder, 123 | lockedHolder, 124 | lockedHolder, 125 | lockedHolder, 126 | lockedHolder 127 | ] 128 | 129 | await fantom.mintTokensLockedMultiple(1, accounts, tokensToMint, terms, {from: owner}) 130 | 131 | let lockedTokens = await fantom.lockedTokens(lockedHolder) 132 | 133 | assert(lockedTokens.cmp(new BigNumber(15)) == 0, `Number of minted tokens locked; expected 15; Got: ${lockedTokens}`) 134 | 135 | // Available Lock Slots 136 | let slotStatus = await rig.fantom.isAvailableLockSlot(lockedHolder, 137 | initialDate.plus(days(2))) 138 | 139 | assert(true == slotStatus, "Lock slot available for same term") 140 | 141 | slotStatus = await rig.fantom.isAvailableLockSlot(lockedHolder, 142 | initialDate.plus(days(6))) 143 | 144 | assert(false == slotStatus, "Lock slot not available for extra term") 145 | 146 | }) 147 | 148 | 149 | it('[mintTokens] should not lock the tokens', async () => { 150 | const rig = await deployer.setupContract(accounts) 151 | const owner = rig.owner 152 | const fantom = rig.fantom 153 | const holder = accounts[2] 154 | 155 | // whitelist the account 156 | await fantom.addToWhitelist(holder, {from: owner}) 157 | 158 | await fantom.mintTokens(1, holder, 10, {from: owner}) 159 | 160 | let lockedTokens = await fantom.lockedTokens(holder) 161 | 162 | assert(lockedTokens.cmp(new BigNumber(0)) == 0, `Number of minted tokens locked; expected 0; Got: ${lockedTokens}`) 163 | 164 | let unlockedTokens = await fantom.unlockedTokens(holder) 165 | assert(unlockedTokens.cmp(new BigNumber(10)) == 0, `Number of minted tokens unlocked; expected 10; Got: ${lockedTokens}`) 166 | }) 167 | }) 168 | 169 | 170 | contract('FantomICODates', (accounts) => { 171 | 172 | 173 | it('should not allow public to change dates', async () => { 174 | const rig = await deployer.setupContract(accounts) 175 | const icoStartTime = rig.ICOStartTime 176 | 177 | let newD = icoStartTime - 10 // Must be before the current start date. 178 | 179 | await helpers.assertRevert(rig.fantom.setDateMainStart(newD, {from: accounts[1]})) 180 | }) 181 | 182 | it('should allow owner to change the dates', async () => { 183 | 184 | const rig = await deployer.setupContract(accounts) 185 | const icoStartTime = rig.ICOStartTime 186 | 187 | let newD = new BigNumber(icoStartTime - 10) // Must be before the current start date. 188 | 189 | await rig.fantom.setDateMainEnd(newD.plus(days(10)), {from: rig.owner}) 190 | await rig.fantom.setDateMainStart(newD, {from: rig.owner}) 191 | 192 | assert(newD.cmp(await rig.fantom.dateMainStart.call()) == 0, 'start date set') 193 | assert(newD.plus(days(10)).cmp(await rig.fantom.dateMainEnd.call()) == 0, 'end date set') 194 | }) 195 | 196 | it('should not allow owner to change dates into past', async() => { 197 | const rig = await deployer.setupContract(accounts) 198 | const icoStartTime = rig.ICOStartTime 199 | 200 | let pastD = new BigNumber(icoStartTime - days(360)) // Must be before the current start date. 201 | 202 | await helpers.assertRevert(rig.fantom.setDateMainEnd(pastD, {from: rig.owner})) 203 | }) 204 | 205 | it('should not allow owner to set start date after or equal to end date', async () => { 206 | 207 | const rig = await deployer.setupContract(accounts) 208 | const icoStartTime = rig.ICOStartTime 209 | 210 | let newD = new BigNumber(icoStartTime) // Must be before the current start date. 211 | 212 | 213 | await rig.fantom.setDateMainEnd(newD.plus(days(10)), {from: rig.owner}) 214 | await helpers.assertRevert(rig.fantom.setDateMainStart(newD.plus(days(11)), {from: rig.owner})) 215 | await helpers.assertRevert(rig.fantom.setDateMainStart(newD.plus(days(10)), {from: rig.owner})) 216 | await rig.fantom.setDateMainStart(newD.plus(days(10)).minus(1), {from: rig.owner}) 217 | 218 | 219 | assert(newD.plus(days(10)).cmp(await rig.fantom.dateMainEnd.call()) == 0, 'end date set') 220 | assert(newD.plus(days(10)).minus(1).cmp(await rig.fantom.dateMainStart.call()) == 0, 'start date set') 221 | }) 222 | 223 | it('[mainsale] should detect main period', async () => { 224 | 225 | const rig = await deployer.setupContract(accounts) 226 | const icoStartTime = rig.icoStartTime 227 | 228 | assert(false == (await rig.fantom.isMainFirstDay()), 'Main should not have started') 229 | 230 | helpers.setDate(timestamp().plus(1000)) 231 | 232 | assert(true == (await rig.fantom.isMainFirstDay()), 'Can detect first day of main') 233 | assert(true == (await rig.fantom.isMain()), 'Can detect main') 234 | 235 | helpers.setDate(timestamp().plus(days(2))) 236 | 237 | assert(false == (await rig.fantom.isMainFirstDay()), 'first day of main finished') 238 | assert(true == (await rig.fantom.isMain()), 'Can detect main') 239 | 240 | helpers.setDate(timestamp().plus(days(14))) 241 | 242 | assert(false == (await rig.fantom.isMainFirstDay()), 'Can detect first day of main') 243 | assert(false == (await rig.fantom.isMain()), 'Can detect main') 244 | }) 245 | 246 | }) 247 | 248 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/tests/gas-consumption.js: -------------------------------------------------------------------------------- 1 | const th = require('../utils/testHelpers'); 2 | const deployer = require('../utils/deployer.js') 3 | const BigNumber = require('bignumber.js') 4 | const Fantom = artifacts.require('./FantomToken.sol') 5 | 6 | // extra global variables 7 | const assertRevert = th.assertRevert; 8 | const blockGasLimit = 8e6; 9 | const days = 3600*24 10 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp); 11 | 12 | const assignTokens = async function(token, to, amount, owner) { 13 | await token.addToWhitelist(to, { from: owner }); 14 | await token.mintTokens(1, to, amount, {from: owner}); 15 | } 16 | 17 | // Setup a function to test gas usage for many Locked Tokens minting 18 | var testMintLockedTokens = function(numberOfAccounts, accounts) { 19 | 20 | it(`[MintTokensLockedMultiple] should cost less than the block gas limit for ${numberOfAccounts} accounts`, async () => { 21 | // Use the modified contract as date needs to be dynamic 22 | let deployedObject = await deployer.setupContract(accounts); 23 | let deployed = deployedObject.fantom; 24 | 25 | // owner to mint tokens. 26 | // set the lock time to the current time plus 3 days 27 | let lockTime = timestamp().plus(3 * days) 28 | let amountOfTokensToLock = 10e5 29 | let mintType = 1 30 | let account = deployedObject.owner // this should already be whitelisted 31 | // white list the owner 32 | await deployed.addToWhitelist(account, {from: deployedObject.owner}) 33 | 34 | let accountList = [] 35 | let amounts = [] 36 | let lockTimeList = [] 37 | 38 | for (i=0; i< numberOfAccounts; i++) { 39 | accountList.push(account); 40 | amounts.push(amountOfTokensToLock* (i+1)*10e5); 41 | lockTimeList.push(lockTime); 42 | } 43 | 44 | let tx = await deployed.mintTokensLockedMultiple(mintType, accountList, amounts, lockTimeList) 45 | 46 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 47 | console.log(`Minting locked tokens for ${numberOfAccounts} accounts. Gas Estimate: ${tx.receipt.gasUsed}`); 48 | 49 | }) 50 | 51 | } 52 | 53 | // Setup a function to test for multiple transfers 54 | var testTransferMultiple = function(numberOfAccounts, accounts) { 55 | 56 | it(`[TransferMultiple] should cost less than the block gas limit for ${numberOfAccounts} accounts`, async () => { 57 | // set up balances 58 | let contract = await deployer.setupContract(accounts); 59 | let deployed = contract.fantom; 60 | let owner = contract.owner 61 | assert(owner === contract.owner); 62 | await assignTokens(deployed, owner, 100e7, owner); 63 | await th.setDate(contract.ICOEndTime + 1); 64 | await deployed.makeTradeable(); 65 | 66 | // build account list 67 | let accountList = [] 68 | let amounts = [] 69 | 70 | for (i=0; i< numberOfAccounts; i++) { 71 | accountList.push(accounts[1]); 72 | amounts.push((i)*1e5); 73 | } 74 | 75 | let tx = await deployed.transferMultiple(accountList, amounts) 76 | 77 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 78 | console.log(`Multiple transfer to ${numberOfAccounts} accounts. Gas Estimate: ${tx.receipt.gasUsed}`); 79 | 80 | }) 81 | 82 | } 83 | 84 | contract('Gas Consumption Tests (optimized-runs = 200)', (accounts) => { 85 | 86 | it("Deployment of contract gas estimate", async () => { 87 | // Use the modified contract as date needs to be dynamic 88 | let deployedObject = await deployer.setupContract(accounts); 89 | let deployed = deployedObject.fantom; 90 | let receipt = await web3.eth.getTransactionReceipt(deployed.transactionHash); 91 | 92 | it("should cost less than the block gas limit to deploy", () => { 93 | assert.isBelow(receipt.gasUsed, blockGasLimit) 94 | }) 95 | console.log("Deployment Gas Estimate: " + receipt.gasUsed); 96 | 97 | }); 98 | 99 | it("should cost less than the block gas limit to buy tokens (optimize-runs = 200)", async () => { 100 | 101 | let deployedObject = await deployer.setupContract(accounts); 102 | let deployed = deployedObject.fantom; 103 | let account = deployedObject.owner // this should already be whitelisted 104 | // white list the owner 105 | await deployed.addToWhitelist(account, {from: deployedObject.owner}) 106 | 107 | // set the time into the ICO Start time 108 | await th.setDate(deployedObject.ICOStartTime + 0.2*days); 109 | 110 | let tx = await deployed.sendTransaction({from: account, value: web3.toWei(2,"ether")}); 111 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 112 | console.log("Buy Tokens Gas Estimate: " + tx.receipt.gasUsed); 113 | }); 114 | 115 | it("should cost less than the block gas limit to mint tokens (optimize-runs = 200)", async () => { 116 | // Use the modified contract as date needs to be dynamic 117 | let deployedObject = await deployer.setupContract(accounts); 118 | let deployed = deployedObject.fantom; 119 | 120 | // owner to mint tokens. 121 | // set the lock time to the current time plus 3 days 122 | let lockTime = timestamp().plus(3 * days) 123 | let amountOfTokensToLock = 10 124 | let mintType = 1 125 | let account = deployedObject.owner // this should already be whitelisted 126 | 127 | // white list the owner 128 | await deployed.addToWhitelist(account, {from: deployedObject.owner}) 129 | 130 | let tx = await deployed.mintTokensLocked(mintType, account, amountOfTokensToLock, lockTime) 131 | 132 | assert.isBelow(tx.receipt.gasUsed, blockGasLimit) 133 | console.log("Minting Locked Tokens Gas Estimate: " + tx.receipt.gasUsed); 134 | }); 135 | 136 | // test the gas costs of minting to mulitple users 137 | testMintLockedTokens(2,accounts) 138 | testMintLockedTokens(5,accounts) 139 | testMintLockedTokens(10,accounts) 140 | testMintLockedTokens(15,accounts) 141 | testMintLockedTokens(20,accounts) 142 | testMintLockedTokens(30,accounts) 143 | testMintLockedTokens(50,accounts) 144 | 145 | // test the gas costs of multiple transfers 146 | testTransferMultiple(2,accounts) 147 | testTransferMultiple(5,accounts) 148 | testTransferMultiple(10,accounts) 149 | testTransferMultiple(15,accounts) 150 | testTransferMultiple(20,accounts) 151 | testTransferMultiple(30,accounts) 152 | testTransferMultiple(50,accounts) 153 | }); 154 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/tests/token-math.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require("bignumber.js") 2 | const helpers = require('../utils/testHelpers.js') 3 | const deployer = require('../utils/deployer.js') 4 | 5 | const decimals = 1e18 6 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp) 7 | const tokenRate = new BigNumber(15000) 8 | const tokenSupply = new BigNumber(3175000000*decimals) 9 | const minContrib = new BigNumber(0.2*decimals) 10 | const tokenMainCap = new BigNumber(50000000*decimals) 11 | const mainLimit = 100 12 | 13 | const sendEther = (contract, account, etherAmount) => { 14 | let weiSent = etherAmount*decimals 15 | return contract.sendTransaction({from:account, value: weiSent}); 16 | } 17 | 18 | contract('[FantomToken - Token Math]', function(accounts) { 19 | 20 | it('should have the correct caps', async function() { 21 | let contract = await deployer.setupContract(accounts) 22 | let fantom = contract.fantom 23 | 24 | let actualTS = await fantom.MAX_TOTAL_TOKEN_SUPPLY.call() 25 | let actualTMC = await fantom.TOKEN_MAIN_CAP.call() 26 | let actualMC = await fantom.MINIMUM_CONTRIBUTION.call() 27 | 28 | assert(tokenSupply.cmp(actualTS) == 0, `TS || Expected: ${tokenSupply}; Got: ${actualTS}`) 29 | assert(minContrib.cmp(actualMC) == 0, `MC || Expected: ${minContrib}; Got: ${actualMC}`) 30 | assert(tokenMainCap.cmp(actualTMC) == 0, `TMC || Expected: ${tokenMainCap}; Got: ${actualTMC}`) 31 | }) 32 | 33 | it('should have a correct token rate for 1 ether', async function() { 34 | let contract = await deployer.setupContract(accounts) 35 | let fantom = contract.fantom 36 | let owner = contract.owner 37 | 38 | // white list account 39 | await fantom.addToWhitelist(accounts[1], {from: owner}) 40 | icodate = contract.ICOStartTime + 200 41 | await helpers.setDate(icodate) 42 | 43 | let etherSent = 1 44 | let weiSent = etherSent*decimals 45 | 46 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 47 | let expectedTokens = tokenRate*etherSent*decimals 48 | 49 | let actualTokens = await fantom.balanceOf(accounts[1]); 50 | assert.equal(expectedTokens,actualTokens.toNumber()) 51 | 52 | }) 53 | 54 | it('should not give more tokens than allowed during first day', async function() { 55 | let contract = await deployer.setupContract(accounts) 56 | let fantom = contract.fantom 57 | let owner = contract.owner 58 | 59 | // white list account 60 | await fantom.addToWhitelist(accounts[1], {from: owner}) 61 | icodate = contract.ICOStartTime + 2000 62 | await helpers.setDate(icodate) 63 | 64 | let tokenLimit = await fantom.firstDayTokenLimit() 65 | let ethLimit = tokenLimit/tokenRate/decimals 66 | let etherSent = ethLimit + 0.001 67 | let weiSent = etherSent*decimals 68 | 69 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 70 | let expectedTokens = new BigNumber(mainLimit*decimals).mul(new BigNumber(15000)) 71 | 72 | let actualTokens = await fantom.balanceOf(accounts[1]); 73 | assert(expectedTokens.cmp(actualTokens) == 0, 74 | `Expected: ${expectedTokens}; Got: ${actualTokens}`) 75 | }) 76 | 77 | it('a user should not be able to purchase more than the token limit in the first day', async function() { 78 | let contract = await deployer.setupContract(accounts) 79 | let fantom = contract.fantom 80 | let owner = contract.owner 81 | 82 | // white list account 83 | await fantom.addToWhitelist(accounts[1], {from: owner}) 84 | icodate = contract.ICOStartTime + 2000 85 | await helpers.setDate(icodate) 86 | 87 | let tokenLimit = await fantom.firstDayTokenLimit() 88 | let ethLimit = tokenLimit/tokenRate/decimals 89 | let etherSent = ethLimit + 0.001 90 | let weiSent = etherSent*decimals 91 | 92 | // send more than the maximum 93 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 94 | // then send more 95 | helpers.assertRevert(fantom.sendTransaction({from:accounts[1], value: weiSent})) 96 | }) 97 | 98 | it('should purchase correct amount of tokens if all whitelisted users purchase tokens', async function() { 99 | let contract = await deployer.setupContract(accounts) 100 | let fantom = contract.fantom 101 | let owner = contract.owner 102 | 103 | // white list account 104 | await fantom.addToWhitelist(accounts[1], {from: owner}) 105 | await fantom.addToWhitelist(accounts[2], {from: owner}) 106 | await fantom.addToWhitelist(accounts[3], {from: owner}) 107 | await fantom.addToWhitelist(accounts[4], {from: owner}) 108 | await fantom.addToWhitelist(accounts[5], {from: owner}) 109 | 110 | icodate = contract.ICOStartTime + 2000 111 | await helpers.setDate(icodate) 112 | 113 | // let whitelisted = await fantom.numberWhitelisted.call() 114 | // let tokenLimit = tokenMainCap/whitelisted 115 | let tokenLimit = await fantom.firstDayTokenLimit() 116 | let ethLimit = tokenLimit/tokenRate/decimals 117 | let etherSent = ethLimit + 0.001 118 | let weiSent = etherSent*decimals 119 | 120 | // send more than the maximum 121 | await fantom.sendTransaction({from:accounts[1], value: weiSent}); 122 | await fantom.sendTransaction({from:accounts[2], value: weiSent}); 123 | await fantom.sendTransaction({from:accounts[3], value: weiSent}); 124 | await fantom.sendTransaction({from:accounts[4], value: weiSent}); 125 | await fantom.sendTransaction({from:accounts[5], value: weiSent}); 126 | 127 | let actualTokens = await fantom.totalSupply(); 128 | let expectedTokens = new BigNumber(mainLimit * decimals).mul(tokenRate).mul(5) 129 | 130 | assert(expectedTokens.cmp(actualTokens) == 0, 131 | `Expected: ${expectedTokens}; Got: ${actualTokens}`) 132 | }) 133 | 134 | 135 | it('[Scenario.1] should give the correct token amounts for scenario 1', async function() { 136 | 137 | let contract = await deployer.setupContract(accounts) 138 | let fantom = contract.fantom 139 | let owner = contract.owner 140 | 141 | // white list account 142 | await fantom.addToWhitelist(accounts[1], {from: owner}) 143 | await fantom.addToWhitelist(accounts[2], {from: owner}) 144 | await fantom.addToWhitelist(accounts[3], {from: owner}) 145 | await fantom.addToWhitelist(accounts[4], {from: owner}) 146 | await fantom.addToWhitelist(accounts[5], {from: owner}) 147 | // set start time 148 | let icodate = contract.ICOStartTime + 2000 149 | await helpers.setDate(icodate) 150 | 151 | // list of ether to be sent 152 | ethSent = [0, 130, 0.3, 0.6,12,2] 153 | expectedReturns = [0, mainLimit, 0.3, 0.6,12,2] 154 | // list of tokens we expect as a result 155 | expectedTokens = expectedReturns.map(x => x*tokenRate*decimals) 156 | 157 | // account 1 spends 30 eth during first day 158 | await sendEther(fantom, accounts[1], ethSent[1]) 159 | // account 2 spends 176 eth during first day 160 | await sendEther(fantom, accounts[2], ethSent[2]) 161 | // account 3 spends 1000 eth during first day 162 | await sendEther(fantom, accounts[3], ethSent[3]) 163 | 164 | // Move to after the first day 165 | await helpers.setDate(contract.ICOStartTime+ 3600*24*2) 166 | 167 | // account 4 spends 12 eth after the first day 168 | await sendEther(fantom, accounts[4], 12) 169 | // account 5 spends 2 eth after the first day 170 | await sendEther(fantom, accounts[5], 2) 171 | 172 | actualTokenBalances = [0] 173 | for (i=1; i<=5; i++) { 174 | actualTokenBalances.push((await fantom.balanceOf(accounts[i])).toNumber()); 175 | } 176 | 177 | for (i=1; i<=5; i++) { 178 | assert(expectedTokens[i] == actualTokenBalances[i], 179 | `${i}: token balances are not as expected: ` + 180 | `Expected:${expectedTokens[i]};` + 181 | `Got: ${actualTokenBalances[i]}`) 182 | } 183 | }) 184 | 185 | it('[Scenario 2] should give the correct token amounts for scenario 1', async function() { 186 | 187 | let contract = await deployer.setupContract(accounts) 188 | let fantom = contract.fantom 189 | let owner = contract.owner 190 | 191 | // white list account 192 | await fantom.addToWhitelist(accounts[1], {from: owner}) 193 | await fantom.addToWhitelist(accounts[2], {from: owner}) 194 | await fantom.addToWhitelist(accounts[3], {from: owner}) 195 | await fantom.addToWhitelist(accounts[4], {from: owner}) 196 | await fantom.addToWhitelist(accounts[5], {from: owner}) 197 | // set start time 198 | let icodate = contract.ICOStartTime + 2000 199 | await helpers.setDate(icodate) 200 | 201 | // list of ether to be sent 202 | ethSent = [10e6, 176, 1e3,12,2] 203 | expectedReturns = [mainLimit, mainLimit, mainLimit, 12, 2] 204 | // list of tokens we expect as a result 205 | expectedTokens = expectedReturns.map(x => x*tokenRate*decimals) 206 | 207 | // account 1 spends 10e6 eth during first day 208 | await sendEther(fantom, accounts[1], ethSent[0]) 209 | // account 2 spends 176 eth during first day 210 | await sendEther(fantom, accounts[2], ethSent[1]) 211 | // account 3 spends 1000 eth during first day 212 | await sendEther(fantom, accounts[3], ethSent[2]) 213 | 214 | // Move to after the first day 215 | await helpers.setDate(contract.ICOStartTime+ 3600*24*2) 216 | 217 | // account 4 spends 12 eth after the first day 218 | await sendEther(fantom, accounts[4], ethSent[3]) 219 | // account 5 spends 2 eth after the first day 220 | await sendEther(fantom, accounts[5], ethSent[4]) 221 | 222 | actualTokenBalances = [] 223 | for (i=1; i<=5; i++) { 224 | actualTokenBalances.push((await fantom.balanceOf(accounts[i])).toNumber()); 225 | } 226 | 227 | for (i=0; i<5; i++) { 228 | assert(expectedTokens[i] == actualTokenBalances[i], `token balance for account ${i+1} is not as expected: expected ${expectedTokens[i]} got: ${actualTokenBalances[i]}`) 229 | } 230 | }) 231 | 232 | }) 233 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/tests/tokensale.js: -------------------------------------------------------------------------------- 1 | 2 | const BigNumber = require("bignumber.js") 3 | const th = require('../utils/testHelpers'); 4 | const deployer = require('../utils/deployer.js') 5 | const assertRevert = th.assertRevert; 6 | 7 | contract('Token Sale', function (accounts) { 8 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 9 | const owner = accounts[0]; 10 | const recipient = accounts[1]; 11 | const anotherAccount = accounts[2]; 12 | const yetAnotherAccount = accounts[3]; 13 | const evenYetAnotherAccount = accounts[4]; 14 | 15 | beforeEach(async function () { 16 | let contract = await deployer.setupContract(accounts); 17 | this.token = contract.fantom; 18 | this.IcoStartDate = contract.ICOStartTime; 19 | assert(owner === contract.owner); 20 | }); 21 | 22 | describe('when the tokensale gets completed the first day', function () { 23 | it('reverts when there is no more tokens to buy and total cap is purchased', async function () { 24 | let tokensPerEth = (await this.token.tokensPerEth.call()) 25 | let maxFirstDay = (await this.token.MAXIMUM_FIRST_DAY_CONTRIBUTION.call()) 26 | let totalTokenCap = await this.token.TOKEN_MAIN_CAP.call() 27 | let numberOfPeopleRequired = totalTokenCap/tokensPerEth/maxFirstDay; 28 | await th.setDate(this.IcoStartDate+ 100); 29 | let weiSent = maxFirstDay*1.1 30 | let totalTokens = new BigNumber(0); 31 | let currentBalance = 0; 32 | 33 | for (i=0; i< numberOfPeopleRequired-1; i++){ 34 | await this.token.addToWhitelist(accounts[i], {from: owner}) 35 | await this.token.sendTransaction({from: accounts[i], value: weiSent}); 36 | currentBalance = await this.token.balanceOf(accounts[i]) 37 | assert.equal(currentBalance,tokensPerEth*maxFirstDay); 38 | totalTokens = totalTokens.add(currentBalance); 39 | } 40 | await this.token.addToWhitelist(accounts[i+1], {from: owner}) 41 | await this.token.sendTransaction({from:accounts[i+1], value: maxFirstDay*1.1}); 42 | currentBalance = await this.token.balanceOf(accounts[i+1]) 43 | totalTokens = totalTokens.add(currentBalance); 44 | 45 | assert.equal(totalTokens.toNumber(), totalTokenCap.toNumber()) 46 | // Any further should revert 47 | await this.token.addToWhitelist(accounts[i+1], {from: owner}) 48 | await assertRevert(this.token.sendTransaction({from:accounts[i+1], value: maxFirstDay*1.1})); 49 | }); 50 | }); 51 | 52 | describe('when the tokensale gets completed after the first day', function () { 53 | it('reverts when there is no more tokens to buy and total cap is purchased', async function () { 54 | let tokensPerEth = (await this.token.tokensPerEth.call()) 55 | let maxFirstDay = (await this.token.MAXIMUM_FIRST_DAY_CONTRIBUTION.call()) 56 | let totalTokenCap = await this.token.TOKEN_MAIN_CAP.call() 57 | let weiSent = 100 * 1e18 58 | let numberOfPeopleRequired = totalTokenCap/tokensPerEth/weiSent; 59 | await th.setDate(this.IcoStartDate + 100 + 3600*24*2); 60 | let totalTokens = new BigNumber(0); 61 | let currentBalance = 0; 62 | 63 | for (i=0; i< numberOfPeopleRequired-1; i++){ 64 | await this.token.addToWhitelist(accounts[i], {from: owner}) 65 | await this.token.sendTransaction({from: accounts[i], value: weiSent}); 66 | currentBalance = await this.token.balanceOf(accounts[i]) 67 | assert.equal(currentBalance,tokensPerEth*weiSent); 68 | totalTokens = totalTokens.add(currentBalance); 69 | } 70 | await this.token.addToWhitelist(accounts[i+1], {from: owner}) 71 | await this.token.sendTransaction({from:accounts[i+1], value: maxFirstDay*1.1}); 72 | currentBalance = await this.token.balanceOf(accounts[i+1]) 73 | totalTokens = totalTokens.add(currentBalance); 74 | 75 | assert.equal(totalTokens.toNumber(), totalTokenCap.toNumber()) 76 | // Any further should revert 77 | await this.token.addToWhitelist(accounts[i+1], {from: owner}) 78 | await assertRevert(this.token.sendTransaction({from:accounts[i+1], value: weiSent})); 79 | }); 80 | }); 81 | 82 | }); 83 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/tests/transferMultiple.js: -------------------------------------------------------------------------------- 1 | /* 2 | * These tests were adapted from the OpenZepplin repository here: 3 | * 4 | * https://github.com/OpenZeppelin/openzeppelin-solidity 5 | */ 6 | 7 | const th = require('../utils/testHelpers'); 8 | const deployer = require('../utils/deployer.js') 9 | const assertRevert = th.assertRevert; 10 | 11 | contract('StandardToken', function (accounts) { 12 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 13 | const owner = accounts[0]; 14 | const recipient = accounts[1]; 15 | const anotherAccount = accounts[2]; 16 | const yetAnotherAccount = accounts[3]; 17 | const evenYetAnotherAccount = accounts[4]; 18 | const mintType = 1; 19 | const assignTokens = async function(token, to, amount) { 20 | await token.addToWhitelist(to, { from: owner }); 21 | await token.mintTokens(mintType, to, 100, {from: owner}); 22 | } 23 | 24 | beforeEach(async function () { 25 | let contract = await deployer.setupContract(accounts); 26 | this.token = contract.fantom; 27 | assert(owner === contract.owner); 28 | await assignTokens(this.token, owner, 100); 29 | await th.setDate(contract.ICOEndTime + 1); 30 | await this.token.makeTradeable(); 31 | }); 32 | 33 | describe('transferMultiple', function () { 34 | describe('when the recipient is not the zero address', function () { 35 | const to = [recipient, anotherAccount, yetAnotherAccount, evenYetAnotherAccount]; 36 | const amount = [25, 25, 25, 25] 37 | 38 | describe('when the sender does not have enough balance', function () { 39 | const tooMuch = amount.map(x => x + 1) 40 | 41 | it('reverts', async function () { 42 | await assertRevert(this.token.transferMultiple(to, tooMuch, { from: owner })); 43 | }); 44 | }); 45 | 46 | describe('when the sender has enough balance', function () { 47 | 48 | it('transfers the requested amount', async function () { 49 | await this.token.transferMultiple(to, amount, { from: owner }); 50 | 51 | const senderBalance = await this.token.balanceOf(owner); 52 | assert.equal(senderBalance, 0); 53 | 54 | for(var i = 0; i < to.length; i++) { 55 | assert.equal(await this.token.balanceOf(to[i]), amount[i]); 56 | } 57 | }); 58 | 59 | it('emits a transfer event', async function () { 60 | const { logs } = await this.token.transferMultiple(to, amount, { from: owner }); 61 | 62 | assert.equal(logs.length, to.length); 63 | for(var i = 0; i < to.length; i++) { 64 | assert.equal(logs[i].event, 'Transfer'); 65 | assert.equal(logs[i].args._from, owner); 66 | assert.equal(logs[i].args._to, to[i]); 67 | assert(logs[i].args._value.eq(amount[i])); 68 | } 69 | }); 70 | }); 71 | }); 72 | 73 | describe('when the recipient is the zero address', function () { 74 | const to = [ZERO_ADDRESS, ZERO_ADDRESS]; 75 | const amount = [50, 50]; 76 | 77 | it('reverts', async function () { 78 | await assertRevert(this.token.transferMultiple(to, amount, { from: owner })); 79 | }); 80 | }); 81 | }); 82 | 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/tests/transferMultipleExtended.js: -------------------------------------------------------------------------------- 1 | /* 2 | * These tests were adapted from the OpenZepplin repository here: 3 | * 4 | * https://github.com/OpenZeppelin/openzeppelin-solidity 5 | */ 6 | 7 | const th = require('../utils/testHelpers'); 8 | const deployer = require('../utils/deployer.js') 9 | const assertRevert = th.assertRevert; 10 | 11 | contract('StandardToken TransferMultiple Extended', function (accounts) { 12 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 13 | const owner = accounts[0]; 14 | const recipient = accounts[1]; 15 | const anotherAccount = accounts[2]; 16 | const yetAnotherAccount = accounts[3]; 17 | const evenYetAnotherAccount = accounts[4]; 18 | const mintType = 1; 19 | const assignTokens = async function(token, to, amount) { 20 | await token.addToWhitelist(to, { from: owner }); 21 | await token.mintTokens(mintType, to, 100, {from: owner}); 22 | } 23 | const assignTokensLocked = async function(token, to, amount, lockTime) { 24 | await token.addToWhitelist(to, { from: owner }); 25 | await token.mintTokensLocked(mintType, to, 100, lockTime, {from: owner}); 26 | } 27 | 28 | beforeEach(async function () { 29 | let contract = await deployer.setupContract(accounts); 30 | this.token = contract.fantom; 31 | assert(owner === contract.owner); 32 | await assignTokens(this.token, owner, 100); 33 | await assignTokensLocked(this.token,owner, 100, contract.ICOEndTime + 3600*24*10); 34 | await th.setDate(contract.ICOEndTime + 1); 35 | }); 36 | 37 | describe('when tokens are not tradeable', function () { 38 | const to = [recipient, anotherAccount, yetAnotherAccount, evenYetAnotherAccount]; 39 | const amount = [25, 25, 25, 25] 40 | 41 | it('reverts', async function () { 42 | await assertRevert(this.token.transferMultiple(to, amount, { from: owner })); 43 | }); 44 | }); 45 | 46 | describe('when the sender has enough unlocked balance', function () { 47 | const to = [recipient, anotherAccount, yetAnotherAccount, evenYetAnotherAccount]; 48 | const amount = [25, 25, 25, 25] 49 | 50 | it('transfers the requested amount', async function () { 51 | await this.token.makeTradeable(); 52 | await this.token.transferMultiple(to, amount, { from: owner }); 53 | 54 | const senderBalance = await this.token.balanceOf(owner); 55 | assert.equal(senderBalance.toNumber(), 100); 56 | 57 | for(var i = 0; i < to.length; i++) { 58 | assert.equal(await this.token.balanceOf(to[i]), amount[i]); 59 | } 60 | }); 61 | 62 | it('emits a transfer event', async function () { 63 | await this.token.makeTradeable(); 64 | const { logs } = await this.token.transferMultiple(to, amount, { from: owner }); 65 | 66 | assert.equal(logs.length, to.length); 67 | for(var i = 0; i < to.length; i++) { 68 | assert.equal(logs[i].event, 'Transfer'); 69 | assert.equal(logs[i].args._from, owner); 70 | assert.equal(logs[i].args._to, to[i]); 71 | assert(logs[i].args._value.eq(amount[i])); 72 | } 73 | }); 74 | }); 75 | 76 | describe('when the sender doesn\'t have enough unlocked balance', async function () { 77 | const to = [recipient, anotherAccount, yetAnotherAccount, evenYetAnotherAccount]; 78 | const tooMuch = [50, 50, 25, 25] 79 | 80 | it('reverts', async function () { 81 | await this.token.makeTradeable(); 82 | await assertRevert(this.token.transferMultiple(to, tooMuch, { from: owner })); 83 | }); 84 | }); 85 | 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/utils/deployer.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | const Fantom = artifacts.require('./FantomToken.sol') 3 | const th = require('./testHelpers.js') 4 | 5 | const days = 3600*24 6 | const timestamp = () => new BigNumber(web3.eth.getBlock(web3.eth.blockNumber).timestamp) 7 | 8 | module.exports.setupContract = async function(accounts) { 9 | 10 | await th.mineOne(); 11 | let owner = accounts[0] 12 | ICOStartTime = parseInt(timestamp()) + 1000; 13 | ICOEndTime = ICOStartTime + 5 *days 14 | let fantom = await Fantom.new(ICOStartTime, ICOEndTime, {from: owner}) 15 | 16 | return {owner, fantom, ICOStartTime, ICOEndTime} 17 | } 18 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/test/utils/testHelpers.js: -------------------------------------------------------------------------------- 1 | var BigNumber = require("bignumber.js"); 2 | 3 | /** 4 | * Collection of Helper Function for Testing 5 | */ 6 | 7 | 8 | /** 9 | * Checks if the given string is an address 10 | * 11 | * @method isAddress 12 | * @param {String} address the given HEX adress 13 | * @return {Boolean} 14 | */ 15 | var isAddress = function (address) { 16 | if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { 17 | // check if it has the basic requirements of an address 18 | return false; 19 | } else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) { 20 | // If it's all small caps or all all caps, return true 21 | return true; 22 | } else { 23 | // Otherwise check each case 24 | return isChecksumAddress(address); 25 | } 26 | }; 27 | 28 | /** 29 | * Checks if the given string is a checksummed address 30 | * 31 | * @method isChecksumAddress 32 | * @param {String} address the given HEX adress 33 | * @return {Boolean} 34 | */ 35 | var isChecksumAddress = function (address) { 36 | // Check each case 37 | address = address.replace('0x',''); 38 | var addressHash = sha3(address.toLowerCase()); 39 | for (var i = 0; i < 40; i++ ) { 40 | // the nth letter should be uppercase if the nth digit of casemap is 1 41 | if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) { 42 | return false; 43 | } 44 | } 45 | return true; 46 | }; 47 | 48 | /* Assertion Helper Functions */ 49 | exports.assertVariable = function(contract, variable, value, message) { 50 | return variable.call() 51 | .then(function(result) { 52 | assert(result === value, message); 53 | return contract; 54 | }); 55 | }; 56 | 57 | exports.assertEthAddress = function(contract, variable, variableName) { 58 | return variable.call() 59 | .then(function(address) { 60 | assert(isAddress(address), variableName + " should be an ethereum address"); 61 | return contract; 62 | }); 63 | }; 64 | 65 | exports.assertBigNumberVariable = function(contract, variable, value, message) { 66 | return variable.call() 67 | .then(function(result) { 68 | assert(result.equals(value), message + ' ' + result.toString() + ' != ' + value); 69 | return contract; 70 | }); 71 | }; 72 | 73 | exports.assertBalanceOf = function(contract,address, value) { 74 | return contract.balanceOf.call(address) 75 | .then(function(result) { 76 | assert(result.equals(value), "balance of " + address + " should be as expected." + ' ' + result.toString() + ' != ' + value); 77 | return contract; 78 | }) 79 | }; 80 | 81 | exports.assertBigNumberArrayVariable = function(contract, variable, i, value, message) { 82 | return variable.call(i) 83 | .then(function(result) { 84 | assert(result.equals(value), message + ' ' + result.toString() + ' != ' + value); 85 | return contract; 86 | }); 87 | }; 88 | 89 | /* 90 | * Check for a throw 91 | */ 92 | exports.assertThrow = function(error) { 93 | assert.include(error.message, 'revert', 'expecting a throw/revert'); 94 | } 95 | 96 | /* 97 | * Assert that an async call threw with a revert. 98 | */ 99 | exports.assertRevert = async promise => { 100 | try { 101 | await promise; 102 | assert.fail('Expected revert not received'); 103 | } catch (error) { 104 | const revertFound = error.message.search('revert') >= 0; 105 | assert(revertFound, `Expected "revert", got ${error} instead`); 106 | } 107 | }; 108 | 109 | 110 | /* 111 | * Check the ETH the sending account gained as a result 112 | * of the transaction. This is useful for calls which 113 | * send the caller ETH, eg a 'sellTokenToEth' function. 114 | */ 115 | exports.ethReturned = async (promise , address) => { 116 | const balanceBefore = web3.eth.getBalance(address) 117 | const tx = await promise 118 | const txEthCost = web3.eth.gasPrice.times(tx.receipt.gasUsed) 119 | .times(5) // I have no idea why this 5x is required 120 | return web3.eth.getBalance(address) 121 | .minus(balanceBefore.minus(txEthCost)) 122 | } 123 | 124 | /* 125 | * Assert that `a` and `b` are equal to each other, 126 | * allowing for a `margin` of error as a percentage 127 | * of `b`. 128 | */ 129 | exports.withinPercentageOf = (a, b, percentage) => { 130 | ba = new BigNumber(a) 131 | bb = new BigNumber(b) 132 | bm = new BigNumber(percentage).dividedBy(100) 133 | return (ba.minus(bb).abs().lessThan(bb.times(bm))) 134 | } 135 | 136 | 137 | /* 138 | * TestRPC Helper functions 139 | * 140 | * These are a collection of functions to move the date and block number 141 | * of testRPC to verify contracts. 142 | */ 143 | 144 | /* Function to emulate block mining 145 | * @param - count - The number of blocks to mine 146 | */ 147 | exports.mineBlocks = function(count) { 148 | const mine = function(c, callback) { 149 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine"}) 150 | .then(() => { 151 | if (c > 1) { 152 | mine(c-1, callback); 153 | } else { 154 | callback() 155 | } 156 | }); 157 | /* 158 | request({ 159 | url: "http://localhost:8545", 160 | method: "POST", 161 | json: true, // <--Very important!!! 162 | body: { 163 | jsonrpc: "2.0", 164 | method: "evm_mine" 165 | } 166 | }, function (error, response, body){ 167 | if(c > 1) { 168 | mine(c -1, callback); 169 | } else { 170 | callback() 171 | } 172 | }); 173 | */ 174 | }; 175 | return new Promise(function(resolve, reject) { 176 | mine(count, function() { 177 | resolve(); 178 | }) 179 | }) 180 | }; 181 | 182 | exports.mineOne = function() { 183 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine"}) 184 | } 185 | 186 | /* TestRPC Snapshots 187 | * 188 | * Takes a snapshot of the current blockchain 189 | */ 190 | exports.takeSnapShot = async () => { 191 | let id = await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_snapshot"}); 192 | return id['result'] 193 | } 194 | 195 | /* 196 | * Reverts to a previous snapshot id. 197 | * @param - snapshotId - The snapshot to revert to. 198 | */ 199 | exports.revertTestRPC = function(snapshotId){ 200 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_revert", params: [snapshotId]}) 201 | } 202 | 203 | /* Sets the date of the testRPC instance to a future datetime. 204 | * @param - date - uint variable represenint a future datetime. 205 | */ 206 | exports.setDate = function(date) { 207 | // Get the current blocknumber. 208 | blockNumber = web3.eth.blockNumber; 209 | curTimeStamp = web3.eth.getBlock(blockNumber).timestamp; 210 | assert(date > curTimeStamp, "the modified date must be in the future"); 211 | // Set the new time. 212 | deltaIncrease = date - curTimeStamp; 213 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [deltaIncrease]}) 214 | // mine a block to ensure we get a new timestamp 215 | web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine"}) 216 | }; 217 | 218 | exports.sleep = async (sleeptime) => { 219 | return new Promise((resolve) => { 220 | setTimeout(resolve, sleeptime) 221 | }) 222 | } 223 | -------------------------------------------------------------------------------- /audits/testsuite/fantom-audit-truffle-two/truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | }, 9 | solc: { 10 | optimizer: { 11 | enabled: true, 12 | runs: 200 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lottery/README.md: -------------------------------------------------------------------------------- 1 | # Fantom Foundation FTM whitelisting lottery 2 | 3 | [FANTOM Whitelisting Lottery Overview](https://medium.com/fantomfoundation/fantom-whitelisting-lottery-overview-363c4275c677) 4 | 5 | ## Stage 1 Lottery Inputs 6 | 7 | The lottery data is in [lottery_1_input.txt](https://github.com/Fantom-foundation/tokensale/blob/master/lottery/lottery_1_input.txt) 8 | 9 | SHA256 hash = 208a6b65b503ba3eab58348053afca240040518c0dffd94466a4847417250162 10 | 11 | https://etherscan.io/tx/0x5eed1f62bf05452602ab7886e18762110591883e74127e0a9daa3e308426b913 12 | 13 | Decision at 12-JUN-2018 @ 17:00 UTC -- corresponding to NIST beacon at 06/12/2018 7:00 pm (https://beacon.nist.gov/home) 14 | 15 | ## Stage 1 Lottery Results 16 | 17 | The lottery results are [lottery_1_result.txt](https://github.com/Fantom-foundation/tokensale/blob/master/lottery/lottery_1_result.txt) 18 | 19 | Here is the NIST beacon value at 06/12/2018 7:00 pm (https://beacon.nist.gov/home): 20 | 21 | E7FB52653FDCEC8562C226C14526B8B877AA99AD89D55EBA402BCF70A981E6CD 22 | F4B3A42F2AE2FF68804795B92C01C7534105B66CBDE3767577B2B92394D14C72 23 | 24 | The corresponding base 10 number R is: 25 | 26 | 12149868849766449211901153489073573982487128517210919352090281003926692678537014332180530904275769738130124240289475481025464029755662550987151049132952690 27 | 28 | We had 84,631 randomly shuffled addresses, and R % 84,631 = 20,220 wich can be seen for example [here](http://www.calculator.net/big-number-calculator.html?cx=12149868849766449211901153489073573982487128517210919352090281003926692678537014332180530904275769738130124240289475481025464029755662550987151049132952690&cy=84631&cp=20&co=mod) 29 | 30 | Therefore, the lottery winners are the 6,000 addresses starting at index 20,220 in the original list. 31 | 32 | Note that selecting randomly from an ordered list is equivalent to shuffling first and then taking a slice - and this second method can be done more transparently than the first one. 33 | 34 | Lottery results with indices based on the original ordered list: [lottery_1_result_canonical.txt](https://github.com/Fantom-foundation/tokensale/blob/master/lottery/lottery_1_result_canonical.txt) 35 | 36 | ## Stage 2 Lottery Inputs 37 | 38 | This was an additional lottery to select a further 3,600 addresses from the list of remaining 78,631 addresses not seected in the first lottery. 39 | 40 | NIST Beacon decision time: 06/13/2018 00:15 am (https://beacon.nist.gov/home) 41 | corresponding to 12-JUN-2018 22:15:00 UTC 42 | 43 | Lottery data: [lottery_2_input.txt](https://github.com/Fantom-foundation/tokensale/blob/master/lottery/lottery_2_input.txt) 44 | 45 | hash = 0x1cd155018775dcb91834591028704993e67e8ed0c86cf66a0150f9ea0eb87f42 46 | 47 | https://etherscan.io/tx/0x901934bf931ec9372e352e4794b5a8aabf750d207b446b5c63f288328f2f6a93 48 | 49 | The NIST Beacon decision time was 06/13/2018 00:15 am 50 | 51 | ## Stage 2 Lottery Results 52 | 53 | Sorted lottery data: [lottery_2_input_raw.txt](https://github.com/Fantom-foundation/tokensale/blob/master/lottery/lottery_2_input_raw.txt) 54 | 55 | NIST beacon value = 957FD16D5797775FED5AC70D9AB69E35560E8610CDF4C345FD1E35CA36F6D87DE028FC34736C2E2079B3A6C32E29645B3ADA588B3F5981209173E329746BB922 56 | 57 | Lottery result: [lottery_2_result.txt](https://github.com/Fantom-foundation/tokensale/blob/master/lottery/lottery_2_result.txt) 58 | 59 | Result with indices of sorted lottery data: [lottery_2_result_canonical.txt](https://github.com/Fantom-foundation/tokensale/blob/master/lottery/lottery_2_result_canonical.txt) 60 | -------------------------------------------------------------------------------- /sol/FantomToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.23; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // 5 | // Fantom Foundation FTM token public sale contract 6 | // 7 | // For details, please visit: http://fantom.foundation 8 | // 9 | // 10 | // written by Alex Kampa - ak@sikoba.com 11 | // 12 | // ---------------------------------------------------------------------------- 13 | 14 | 15 | // ---------------------------------------------------------------------------- 16 | // 17 | // SafeMath 18 | // 19 | // ---------------------------------------------------------------------------- 20 | 21 | library SafeMath { 22 | 23 | function add(uint a, uint b) internal pure returns (uint c) { 24 | c = a + b; 25 | require(c >= a); 26 | } 27 | 28 | function sub(uint a, uint b) internal pure returns (uint c) { 29 | require(b <= a); 30 | c = a - b; 31 | } 32 | 33 | function mul(uint a, uint b) internal pure returns (uint c) { 34 | c = a * b; 35 | require(a == 0 || c / a == b); 36 | } 37 | 38 | } 39 | 40 | // ---------------------------------------------------------------------------- 41 | // 42 | // Owned 43 | // 44 | // ---------------------------------------------------------------------------- 45 | 46 | contract Owned { 47 | 48 | address payable public owner; 49 | address payable public newOwner; 50 | 51 | mapping(address => bool) public isAdmin; 52 | 53 | event OwnershipTransferProposed(address indexed _from, address indexed _to); 54 | event OwnershipTransferred(address indexed _from, address indexed _to); 55 | event AdminChange(address indexed _admin, bool _status); 56 | 57 | modifier onlyOwner {require(msg.sender == owner); _;} 58 | modifier onlyAdmin {require(isAdmin[msg.sender]); _;} 59 | 60 | constructor() public { 61 | owner = msg.sender; 62 | isAdmin[owner] = true; 63 | } 64 | 65 | function transferOwnership(address payable _newOwner) public onlyOwner { 66 | require(_newOwner != address(0x0)); 67 | emit OwnershipTransferProposed(owner, _newOwner); 68 | newOwner = _newOwner; 69 | } 70 | 71 | function acceptOwnership() public { 72 | require(msg.sender == newOwner); 73 | emit OwnershipTransferred(owner, newOwner); 74 | owner = newOwner; 75 | } 76 | 77 | function addAdmin(address _a) public onlyOwner { 78 | require(isAdmin[_a] == false); 79 | isAdmin[_a] = true; 80 | emit AdminChange(_a, true); 81 | } 82 | 83 | function removeAdmin(address _a) public onlyOwner { 84 | require(isAdmin[_a] == true); 85 | isAdmin[_a] = false; 86 | emit AdminChange(_a, false); 87 | } 88 | 89 | } 90 | 91 | 92 | // ---------------------------------------------------------------------------- 93 | // 94 | // Wallet 95 | // 96 | // ---------------------------------------------------------------------------- 97 | 98 | contract Wallet is Owned { 99 | 100 | address payable public wallet; 101 | 102 | event WalletUpdated(address payable newWallet); 103 | 104 | constructor() public { 105 | wallet = owner; 106 | } 107 | 108 | function setWallet(address payable _wallet) public onlyOwner { 109 | require(_wallet != address(0x0)); 110 | wallet = _wallet; 111 | emit WalletUpdated(_wallet); 112 | } 113 | 114 | } 115 | 116 | 117 | // ---------------------------------------------------------------------------- 118 | // 119 | // ERC20Interface 120 | // 121 | // ---------------------------------------------------------------------------- 122 | 123 | contract ERC20Interface { 124 | 125 | event Transfer(address indexed _from, address indexed _to, uint _value); 126 | event Approval(address indexed _owner, address indexed _spender, uint _value); 127 | 128 | function totalSupply() public view returns (uint); 129 | function balanceOf(address _owner) public view returns (uint balance); 130 | function transfer(address _to, uint _value) public returns (bool success); 131 | function transferFrom(address _from, address _to, uint _value) public returns (bool success); 132 | function approve(address _spender, uint _value) public returns (bool success); 133 | function allowance(address _owner, address _spender) public view returns (uint remaining); 134 | 135 | } 136 | 137 | 138 | // ---------------------------------------------------------------------------- 139 | // 140 | // ERC Token Standard #20 141 | // 142 | // ---------------------------------------------------------------------------- 143 | 144 | contract ERC20Token is ERC20Interface, Owned { 145 | 146 | using SafeMath for uint; 147 | 148 | uint public tokensIssuedTotal; 149 | mapping(address => uint) balances; 150 | mapping(address => mapping (address => uint)) allowed; 151 | 152 | function totalSupply() public view returns (uint) { 153 | return tokensIssuedTotal; 154 | } 155 | // Includes BOTH locked AND unlocked tokens 156 | 157 | function balanceOf(address _owner) public view returns (uint) { 158 | return balances[_owner]; 159 | } 160 | 161 | function transfer(address _to, uint _amount) public returns (bool) { 162 | require(_to != address(0)); 163 | balances[msg.sender] = balances[msg.sender].sub(_amount); 164 | balances[_to] = balances[_to].add(_amount); 165 | emit Transfer(msg.sender, _to, _amount); 166 | return true; 167 | } 168 | 169 | function approve(address _spender, uint _amount) public returns (bool) { 170 | allowed[msg.sender][_spender] = _amount; 171 | emit Approval(msg.sender, _spender, _amount); 172 | return true; 173 | } 174 | 175 | function transferFrom(address _from, address _to, uint _amount) public returns (bool) { 176 | require(_to != address(0)); 177 | balances[_from] = balances[_from].sub(_amount); 178 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount); 179 | balances[_to] = balances[_to].add(_amount); 180 | emit Transfer(_from, _to, _amount); 181 | return true; 182 | } 183 | 184 | function allowance(address _owner, address _spender) public view returns (uint) { 185 | return allowed[_owner][_spender]; 186 | } 187 | 188 | } 189 | 190 | 191 | // ---------------------------------------------------------------------------- 192 | // 193 | // LockSlots 194 | // 195 | // ---------------------------------------------------------------------------- 196 | 197 | contract LockSlots is ERC20Token { 198 | 199 | using SafeMath for uint; 200 | 201 | uint public constant LOCK_SLOTS = 5; 202 | mapping(address => uint[LOCK_SLOTS]) public lockTerm; 203 | mapping(address => uint[LOCK_SLOTS]) public lockAmnt; 204 | mapping(address => bool) public mayHaveLockedTokens; 205 | 206 | event RegisteredLockedTokens(address indexed account, uint indexed idx, uint tokens, uint term); 207 | 208 | function registerLockedTokens(address _account, uint _tokens, uint _term) internal returns (uint idx) { 209 | require(_term > now, "lock term must be in the future"); 210 | 211 | // find a slot (clean up while doing this) 212 | // use either the existing slot with the exact same term, 213 | // of which there can be at most one, or the first empty slot 214 | idx = 9999; 215 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 216 | uint[LOCK_SLOTS] storage amnt = lockAmnt[_account]; 217 | for (uint i; i < LOCK_SLOTS; i++) { 218 | if (term[i] < now) { 219 | term[i] = 0; 220 | amnt[i] = 0; 221 | if (idx == 9999) idx = i; 222 | } 223 | if (term[i] == _term) idx = i; 224 | } 225 | 226 | // fail if no slot was found 227 | require(idx != 9999, "registerLockedTokens: no available slot found"); 228 | 229 | // register locked tokens 230 | if (term[idx] == 0) term[idx] = _term; 231 | amnt[idx] = amnt[idx].add(_tokens); 232 | mayHaveLockedTokens[_account] = true; 233 | emit RegisteredLockedTokens(_account, idx, _tokens, _term); 234 | } 235 | 236 | // public view functions 237 | 238 | function lockedTokens(address _account) public view returns (uint) { 239 | if (!mayHaveLockedTokens[_account]) return 0; 240 | return pNumberOfLockedTokens(_account); 241 | } 242 | 243 | function unlockedTokens(address _account) public view returns (uint) { 244 | return balances[_account].sub(lockedTokens(_account)); 245 | } 246 | 247 | function isAvailableLockSlot(address _account, uint _term) public view returns (bool) { 248 | if (!mayHaveLockedTokens[_account]) return true; 249 | if (_term < now) return true; 250 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 251 | for (uint i; i < LOCK_SLOTS; i++) { 252 | if (term[i] < now || term[i] == _term) return true; 253 | } 254 | return false; 255 | } 256 | 257 | // internal and private functions 258 | 259 | function unlockedTokensInternal(address _account) internal returns (uint) { 260 | // updates mayHaveLockedTokens if necessary 261 | if (!mayHaveLockedTokens[_account]) return balances[_account]; 262 | uint locked = pNumberOfLockedTokens(_account); 263 | if (locked == 0) mayHaveLockedTokens[_account] = false; 264 | return balances[_account].sub(locked); 265 | } 266 | 267 | function pNumberOfLockedTokens(address _account) private view returns (uint locked) { 268 | uint[LOCK_SLOTS] storage term = lockTerm[_account]; 269 | uint[LOCK_SLOTS] storage amnt = lockAmnt[_account]; 270 | for (uint i; i < LOCK_SLOTS; i++) { 271 | if (term[i] >= now) locked = locked.add(amnt[i]); 272 | } 273 | } 274 | 275 | } 276 | 277 | 278 | // ---------------------------------------------------------------------------- 279 | // 280 | // FantomIcoDates 281 | // 282 | // ---------------------------------------------------------------------------- 283 | 284 | contract FantomIcoDates is Owned { 285 | 286 | uint public dateMainStart = 1629053200; // Sunday, 15 August 2021 18:46:40 287 | uint public dateMainEnd = 1639053200; // Thursday, 9 December 2021 12:33:20 288 | 289 | uint public constant DATE_LIMIT = 1629053200 + 180 days; 290 | 291 | event IcoDateUpdated(uint id, uint unixts); 292 | 293 | // check dates 294 | 295 | modifier checkDateOrder { 296 | _ ; 297 | require ( dateMainStart < dateMainEnd ) ; 298 | require ( dateMainEnd < DATE_LIMIT ) ; 299 | } 300 | 301 | constructor() public checkDateOrder() { 302 | require(now < dateMainStart); 303 | } 304 | 305 | // set ico dates 306 | 307 | function setDateMainStart(uint _unixts) public onlyOwner checkDateOrder { 308 | require(now < _unixts && now < dateMainStart); 309 | dateMainStart = _unixts; 310 | emit IcoDateUpdated(1, _unixts); 311 | } 312 | 313 | function setDateMainEnd(uint _unixts) public onlyOwner checkDateOrder { 314 | require(now < _unixts && now < dateMainEnd); 315 | dateMainEnd = _unixts; 316 | emit IcoDateUpdated(2, _unixts); 317 | } 318 | 319 | // where are we? Passed first day or not? 320 | 321 | function isMainFirstDay() public view returns (bool) { 322 | if (now > dateMainStart && now <= dateMainStart + 1 days) return true; 323 | return false; 324 | } 325 | 326 | function isMain() public view returns (bool) { 327 | if (now > dateMainStart && now < dateMainEnd) return true; 328 | return false; 329 | } 330 | 331 | } 332 | 333 | // ---------------------------------------------------------------------------- 334 | // 335 | // Fantom public token sale 336 | // 337 | // ---------------------------------------------------------------------------- 338 | 339 | contract FantomToken is ERC20Token, Wallet, LockSlots, FantomIcoDates { 340 | 341 | // Utility variable 342 | 343 | uint constant E18 = 10**18; 344 | 345 | // Basic token data 346 | 347 | string public constant name = "Fantom Token"; 348 | string public constant symbol = "FTM"; 349 | uint8 public constant decimals = 18; 350 | 351 | // Token number of possible tokens in existance 352 | 353 | uint public constant MAX_TOTAL_TOKEN_SUPPLY = 3175000000 * E18; 354 | 355 | 356 | // crowdsale parameters 357 | // Opening ETH Rate: USD$463.28 358 | // Therefore, 1 ETH = 11582 FTM 359 | 360 | 361 | uint public tokensPerEth = 11582; 362 | 363 | // USD$2,000,000/463.28 = 4317.043668 ether 364 | // 4317.043668 ether/2551 addresses = 1.692294656 ether per address for the first 24 hours 365 | 366 | uint public constant MINIMUM_CONTRIBUTION = 0.2 ether; 367 | uint public constant MAXIMUM_FIRST_DAY_CONTRIBUTION = 1.692294656 ether; 368 | 369 | uint public constant TOKEN_MAIN_CAP = 50000000 * E18; 370 | 371 | bool public tokensTradeable; 372 | 373 | // whitelisting 374 | 375 | mapping(address => bool) public whitelist; 376 | uint public numberWhitelisted; 377 | 378 | // track main sale 379 | 380 | uint public tokensMain; 381 | mapping(address => uint) public balancesMain; 382 | 383 | uint public totalEthContributed; 384 | mapping(address => uint) public ethContributed; 385 | 386 | // tracking tokens minted 387 | 388 | uint public tokensMinted; 389 | mapping(address => uint) public balancesMinted; 390 | mapping(address => mapping(uint => uint)) public balancesMintedByType; 391 | 392 | // migration variable 393 | 394 | bool public isMigrationPhaseOpen; 395 | 396 | // Events --------------------------------------------- 397 | 398 | event UpdatedTokensPerEth(uint tokensPerEth); 399 | event Whitelisted(address indexed account, uint countWhitelisted); 400 | event TokensMinted(uint indexed mintType, address indexed account, uint tokens, uint term); 401 | event RegisterContribution(address indexed account, uint tokensIssued, uint ethContributed, uint ethReturned); 402 | event TokenExchangeRequested(address indexed account, uint tokens); 403 | 404 | // Basic Functions ------------------------------------ 405 | 406 | constructor() public {} 407 | 408 | function () external payable { 409 | buyTokens(); 410 | } 411 | 412 | // Information functions 413 | 414 | 415 | function availableToMint() public view returns (uint) { 416 | return MAX_TOTAL_TOKEN_SUPPLY.sub(TOKEN_MAIN_CAP).sub(tokensMinted); 417 | } 418 | 419 | function firstDayTokenLimit() public view returns (uint) { 420 | return ethToTokens(MAXIMUM_FIRST_DAY_CONTRIBUTION); 421 | } 422 | 423 | function ethToTokens(uint _eth) public view returns (uint tokens) { 424 | tokens = _eth.mul(tokensPerEth); 425 | } 426 | 427 | function tokensToEth(uint _tokens) public view returns (uint eth) { 428 | eth = _tokens / tokensPerEth; 429 | } 430 | 431 | // Admin functions 432 | 433 | function addToWhitelist(address _account) public onlyAdmin { 434 | pWhitelist(_account); 435 | } 436 | 437 | function addToWhitelistMultiple(address[] memory _addresses) public onlyAdmin { 438 | for (uint i; i < _addresses.length; i++) { 439 | pWhitelist(_addresses[i]); 440 | } 441 | } 442 | 443 | function pWhitelist(address _account) internal { 444 | if (whitelist[_account]) return; 445 | whitelist[_account] = true; 446 | numberWhitelisted = numberWhitelisted.add(1); 447 | emit Whitelisted(_account, numberWhitelisted); 448 | } 449 | 450 | // Owner functions ------------------------------------ 451 | 452 | function updateTokensPerEth(uint _tokens_per_eth) public onlyOwner { 453 | require(now < dateMainStart); 454 | tokensPerEth = _tokens_per_eth; 455 | emit UpdatedTokensPerEth(tokensPerEth); 456 | } 457 | 458 | // Only owner can make tokens tradable at any time, or if the date is 459 | // greater than the end of the mainsale date plus 20 weeks, allow 460 | // any caller to make tokensTradeable. 461 | 462 | function makeTradeable() public { 463 | require(msg.sender == owner || now > dateMainEnd + 20 weeks); 464 | tokensTradeable = true; 465 | } 466 | 467 | function openMigrationPhase() public onlyOwner { 468 | require(now > dateMainEnd); 469 | isMigrationPhaseOpen = true; 470 | } 471 | 472 | // Token minting -------------------------------------- 473 | 474 | function mintTokens(uint _mint_type, address _account, uint _tokens) public onlyOwner { 475 | pMintTokens(_mint_type, _account, _tokens, 0); 476 | } 477 | 478 | function mintTokensMultiple(uint _mint_type, address[] memory _accounts, uint[] memory _tokens) public onlyOwner { 479 | require(_accounts.length == _tokens.length); 480 | for (uint i; i < _accounts.length; i++) { 481 | pMintTokens(_mint_type, _accounts[i], _tokens[i], 0); 482 | } 483 | } 484 | 485 | function mintTokensLocked(uint _mint_type, address _account, uint _tokens, uint _term) public onlyOwner { 486 | pMintTokens(_mint_type, _account, _tokens, _term); 487 | } 488 | 489 | function mintTokensLockedMultiple(uint _mint_type, address[] memory _accounts, uint[] memory _tokens, uint[] memory _terms) public onlyOwner { 490 | require(_accounts.length == _tokens.length); 491 | require(_accounts.length == _terms.length); 492 | for (uint i; i < _accounts.length; i++) { 493 | pMintTokens(_mint_type, _accounts[i], _tokens[i], _terms[i]); 494 | } 495 | } 496 | 497 | function pMintTokens(uint _mint_type, address _account, uint _tokens, uint _term) private { 498 | require(whitelist[_account]); 499 | require(_account != address(0)); 500 | require(_tokens > 0); 501 | require(_tokens <= availableToMint(), "not enough tokens available to mint"); 502 | require(_term == 0 || _term > now, "either without lock term, or lock term must be in the future"); 503 | 504 | // register locked tokens (will throw if no slot is found) 505 | if (_term > 0) registerLockedTokens(_account, _tokens, _term); 506 | 507 | // update 508 | balances[_account] = balances[_account].add(_tokens); 509 | balancesMinted[_account] = balancesMinted[_account].add(_tokens); 510 | balancesMintedByType[_account][_mint_type] = balancesMintedByType[_account][_mint_type].add(_tokens); 511 | tokensMinted = tokensMinted.add(_tokens); 512 | tokensIssuedTotal = tokensIssuedTotal.add(_tokens); 513 | 514 | // log event 515 | emit Transfer(address(0), _account, _tokens); 516 | emit TokensMinted(_mint_type, _account, _tokens, _term); 517 | } 518 | 519 | // Main sale ------------------------------------------ 520 | 521 | function buyTokens() private { 522 | 523 | require(isMain()); 524 | require(msg.value >= MINIMUM_CONTRIBUTION); 525 | require(whitelist[msg.sender]); 526 | 527 | uint tokens_available = TOKEN_MAIN_CAP.sub(tokensMain); 528 | 529 | // adjust tokens_available on first day, if necessary 530 | if (isMainFirstDay()) { 531 | uint tokens_available_first_day = firstDayTokenLimit().sub(balancesMain[msg.sender]); 532 | if (tokens_available_first_day < tokens_available) { 533 | tokens_available = tokens_available_first_day; 534 | } 535 | } 536 | 537 | require (tokens_available > 0); 538 | 539 | uint tokens_requested = ethToTokens(msg.value); 540 | uint tokens_issued = tokens_requested; 541 | 542 | uint eth_contributed = msg.value; 543 | uint eth_returned; 544 | 545 | if (tokens_requested > tokens_available) { 546 | tokens_issued = tokens_available; 547 | eth_returned = tokensToEth(tokens_requested.sub(tokens_available)); 548 | eth_contributed = msg.value.sub(eth_returned); 549 | } 550 | 551 | balances[msg.sender] = balances[msg.sender].add(tokens_issued); 552 | balancesMain[msg.sender] = balancesMain[msg.sender].add(tokens_issued); 553 | tokensMain = tokensMain.add(tokens_issued); 554 | tokensIssuedTotal = tokensIssuedTotal.add(tokens_issued); 555 | 556 | ethContributed[msg.sender] = ethContributed[msg.sender].add(eth_contributed); 557 | totalEthContributed = totalEthContributed.add(eth_contributed); 558 | 559 | // ether transfers 560 | if (eth_returned > 0) msg.sender.transfer(eth_returned); 561 | wallet.transfer(eth_contributed); 562 | 563 | // log 564 | emit Transfer(address(0x0), msg.sender, tokens_issued); 565 | emit RegisterContribution(msg.sender, tokens_issued, eth_contributed, eth_returned); 566 | } 567 | 568 | // Token exchange / migration to new platform --------- 569 | 570 | function requestTokenExchangeMax() public { 571 | requestTokenExchange(unlockedTokensInternal(msg.sender)); 572 | } 573 | 574 | function requestTokenExchange(uint _tokens) public { 575 | require(isMigrationPhaseOpen); 576 | require(_tokens > 0 && _tokens <= unlockedTokensInternal(msg.sender)); 577 | balances[msg.sender] = balances[msg.sender].sub(_tokens); 578 | tokensIssuedTotal = tokensIssuedTotal.sub(_tokens); 579 | emit Transfer(msg.sender, address(0x0), _tokens); 580 | emit TokenExchangeRequested(msg.sender, _tokens); 581 | } 582 | 583 | // ERC20 functions ------------------- 584 | 585 | /* Transfer out any accidentally sent ERC20 tokens */ 586 | 587 | function transferAnyERC20Token(address _token_address, uint _amount) public onlyOwner returns (bool success) { 588 | return ERC20Interface(_token_address).transfer(owner, _amount); 589 | } 590 | 591 | /* Override "transfer" */ 592 | 593 | function transfer(address _to, uint _amount) public returns (bool success) { 594 | require(tokensTradeable); 595 | require(_amount <= unlockedTokensInternal(msg.sender)); 596 | return super.transfer(_to, _amount); 597 | } 598 | 599 | /* Override "transferFrom" */ 600 | 601 | function transferFrom(address _from, address _to, uint _amount) public returns (bool success) { 602 | require(tokensTradeable); 603 | require(_amount <= unlockedTokensInternal(_from)); 604 | return super.transferFrom(_from, _to, _amount); 605 | } 606 | 607 | /* Multiple token transfers from one address to save gas */ 608 | 609 | function transferMultiple(address[] calldata _addresses, uint[] calldata _amounts) external { 610 | require(_addresses.length <= 100); 611 | require(_addresses.length == _amounts.length); 612 | 613 | // do the transfers 614 | for (uint j; j < _addresses.length; j++) { 615 | transfer(_addresses[j], _amounts[j]); 616 | } 617 | 618 | } 619 | 620 | } 621 | -------------------------------------------------------------------------------- /sol/FantomTokenTest.sol: -------------------------------------------------------------------------------- 1 | contract FantomTokenTest is FantomToken { 2 | 3 | /* 4 | 5 | Introduces function setTestTime(uint) 6 | 7 | Overrides function atNow() to return testTime instead of now() 8 | 9 | */ 10 | 11 | uint public testTime = 1; 12 | 13 | // Events --------------------------- 14 | 15 | event TestTimeSet(uint _now); 16 | 17 | // Functions ------------------------ 18 | 19 | constructor() public {} 20 | 21 | function atNow() public constant returns (uint) { 22 | return testTime; 23 | } 24 | 25 | function setTestTime(uint _t) public onlyOwner { 26 | testTime = _t; 27 | emit TestTimeSet(_t); 28 | } 29 | 30 | } --------------------------------------------------------------------------------