├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .nvmrc ├── .solcover.js ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── contracts ├── LifChannels.sol ├── LifCrowdsale.sol ├── LifMarketValidationMechanism.sol ├── LifToken.sol ├── LifTokenTest.sol ├── Migrations.sol ├── VestedPayment.sol ├── deploy │ └── TGEDeployer.sol └── test-helpers │ └── Message.sol ├── index.js ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── package-lock.json ├── package.json ├── scripts ├── ci.sh ├── coverage.sh ├── coveralls.sh └── test.sh ├── test ├── Crowdsale.js ├── CrowdsaleGenTest.js ├── LifChannels.js ├── LifToken.js ├── LifTokenTest.js ├── MarketValidationMechanism.js ├── VestedPayment.js ├── commands.js ├── deploy │ ├── TGE Report.md │ ├── TGEDeployer.js │ ├── TGEDistribution.js │ └── deployed_contracts_build │ │ ├── BasicToken.json │ │ ├── BurnableToken.json │ │ ├── ECRecovery.json │ │ ├── ERC20.json │ │ ├── ERC20Basic.json │ │ ├── ERC827.json │ │ ├── ERC827Token.json │ │ ├── LifChannels.json │ │ ├── LifCrowdsale.json │ │ ├── LifMarketValidationMechanism.json │ │ ├── LifToken.json │ │ ├── LifTokenTest.json │ │ ├── Message.json │ │ ├── Migrations.json │ │ ├── MintableToken.json │ │ ├── Ownable.json │ │ ├── Pausable.json │ │ ├── PausableToken.json │ │ ├── SafeMath.json │ │ ├── StandardToken.json │ │ ├── TGEDeployer.json │ │ └── VestedPayment.json ├── generators.js ├── helpers.js └── helpers │ ├── increaseTime.js │ └── latestTime.js ├── tokenList.json ├── truffle-config.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends" : [ 3 | "standard", 4 | "plugin:promise/recommended" 5 | ], 6 | "plugins": [ 7 | "promise" 8 | ], 9 | "env": { 10 | "browser" : true, 11 | "node" : true, 12 | "mocha" : true, 13 | "jest" : true 14 | }, 15 | "globals" : { 16 | "artifacts": false, 17 | "contract": false, 18 | "assert": false, 19 | "web3": false 20 | }, 21 | "rules": { 22 | 23 | // Strict mode 24 | "strict": [2, "global"], 25 | 26 | // Code style 27 | "indent": [2, 2], 28 | "quotes": [2, "single"], 29 | "semi": ["error", "always"], 30 | "space-before-function-paren": ["error", "always"], 31 | "no-use-before-define": 0, 32 | "eqeqeq": [2, "smart"], 33 | "dot-notation": [2, {"allowKeywords": true, "allowPattern": ""}], 34 | "no-redeclare": [2, {"builtinGlobals": true}], 35 | "no-trailing-spaces": [2, { "skipBlankLines": true }], 36 | "eol-last": 1, 37 | "comma-spacing": [2, {"before": false, "after": true}], 38 | "camelcase": [2, {"properties": "always"}], 39 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 40 | "comma-dangle": [1, "always-multiline"], 41 | "no-dupe-args": 2, 42 | "no-dupe-keys": 2, 43 | "no-debugger": 0, 44 | "no-undef": 2, 45 | "one-var": [0], 46 | "object-curly-spacing": [2, "always"], 47 | "generator-star-spacing": ["error", "before"], 48 | "promise/avoid-new": 0, 49 | "promise/always-return": 0 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | .node-xmlhttprequest-sync-[0-9]* 6 | 7 | # Compiled 8 | build 9 | 10 | # Dependency directories 11 | node_modules 12 | 13 | # Coverage artifacts 14 | coverage 15 | coverage.json 16 | scTopics 17 | allFiredEvents 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .node-xmlhttprequest-sync-[0-9]* 4 | coverage 5 | coverage.json 6 | allFiredEvents 7 | scTopics 8 | package-lock.json 9 | contracts 10 | !build/contracts 11 | migrations 12 | scripts 13 | test 14 | .solcover.js 15 | .eslintrc.js 16 | .travis.yml 17 | truffle-config.js 18 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.9.4 2 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 8555, 3 | norpc: true, 4 | copyNodeModules: true, 5 | skipFiles: ['test-helpers/Message.sol', 'deploy/TGEDeployer.sol'] 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | group: beta 4 | language: node_js 5 | node_js: 6 | - "8" 7 | cache: 8 | yarn: true 9 | env: 10 | - 11 | - SOLIDITY_COVERAGE=true 12 | matrix: 13 | fast_finish: true 14 | allow_failures: 15 | - env: SOLIDITY_COVERAGE=true 16 | script: 17 | - ./scripts/ci.sh 18 | before_deploy: npm run buildfornpm 19 | deploy: 20 | provider: npm 21 | email: augusto.lemble@gmail.com 22 | skip_cleanup: true 23 | api_key: $NPM_TOKEN 24 | on: 25 | repo: windingtree/LifToken 26 | tags: true 27 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at conduct@windingtree.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Líf Token 2 | 3 | Líf is the token of the Winding Tree platform. 4 | 5 | Líf is a SmartToken, based in the ERC20 standard with extra methods to send value and data on transfers and approvals, allowing the execution of calls in those methdos too. 6 | 7 | This repository also has all the contracts related with the Token Generation Event (TGE), an strategy that combines a crowdsale, a market validation mechanism and vested payments. 8 | 9 | [![Build Status](https://travis-ci.org/windingtree/LifToken.svg?branch=master)](https://travis-ci.org/windingtree/LifToken) 10 | [![Coverage Status](https://coveralls.io/repos/github/windingtree/LifToken/badge.svg?branch=master)](https://coveralls.io/github/windingtree/LifToken?branch=master&v=2.0) 11 | 12 | ## Requirements 13 | 14 | LTS Node 8.9.4 is required for running the tests. 15 | 16 | ## Install 17 | 18 | ```sh 19 | npm install 20 | ``` 21 | 22 | ## Contracts 23 | 24 | - [LifToken](contracts/LifToken.sol): ERC827 token for the Winding Tree platform. 25 | Uses OpenZeppelin ERC827Token, StandardToken, BurnableToken, MintableToken and PausableToken contracts. 26 | - [LifChannels](contracts/LifChannels.sol): Implementation of simple state channels for Lif token holders. 27 | - [LifCrowdsale](contracts/LifCrowdsale.sol): Implementation of the Lif Token Generation Event (TGE) 28 | Crowdsale: A 2 week fixed price, uncapped token sale, with a discounted rate for contributions during the private 29 | presale and a Market Validation Mechanism that will receive the funds over the USD 10M soft cap. 30 | - [LifMarketValidationMechanism](contracts/LifMarketValidationMechanism.sol) (MVM): holds the ETH received during 31 | the TGE in excess of $10M for a fixed period of time (24 or 48 months depending on the total amount received) releasing 32 | part of the funds to the foundation in a monthly basis with a distribution skewed towards the end (most of the funds are 33 | released by the end of the MVM lifetime). Token holders can send their tokens to the MVM in exchange of eth at a rate 34 | that complements the distribution curve (the rate is higher at the beginning of the MVM and goes towards 0 by the end of it). 35 | - [VestedPayment.sol](contracts/VestedPayment.sol): Handles two time-locked payments: The 5% extra tokens 36 | that the foundation receives for long-term funding (starts after the MVM finishes, with same duration as the MVM: 2 or 4 years) 37 | and the 12.8% extra tokens that the founders receive (1y cliff, 4y total). Both are created during the Crowdsale finalization. 38 | 39 | ## Test 40 | 41 | * To run all tests: `npm test` 42 | 43 | * To run a specific test: `npm test -- test/Crowdsale.js` 44 | 45 | There are also two environment variables (`GEN_TESTS_QTY` and `GEN_TESTS_TIMEOUT`) that regulate the duration/depth of the property-based tests, so for example: 46 | 47 | ```sh 48 | GEN_TESTS_QTY=50 GEN_TESTS_TIMEOUT=300 npm test 49 | ``` 50 | 51 | Will make the property-based tests in `test/CrowdsaleGenTest.js` to run 50 examples in a maximum of 5 minutes 52 | 53 | 54 | ## License 55 | 56 | Líf Token is open source and distributed under the GPL v3 license. 57 | -------------------------------------------------------------------------------- /contracts/LifChannels.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 5 | import "zeppelin-solidity/contracts/ECRecovery.sol"; 6 | 7 | /** 8 | @title LifChannels, State channels for ERC20 Lif Token 9 | 10 | Contract that provides holders of a ERC20 compatible token the creation of 11 | channels between two users, once a channel is open the users can exchange 12 | signatures offchain to agree on the final value of the transfer. 13 | Uses OpenZeppelin ERC20 and SafeMath lib. 14 | */ 15 | contract LifChannels { 16 | using SafeMath for uint256; 17 | 18 | // Add recover method for bytes32 using ECRecovery lib from OpenZeppelin 19 | using ECRecovery for bytes32; 20 | 21 | // The ERC20 token to be used 22 | ERC20 public token; 23 | 24 | // The amount of time that a receiver has to challenge the sender 25 | uint256 public challengeTime; 26 | 27 | // The channels opened 28 | mapping (bytes32 => Channel) public channels; 29 | struct Channel { 30 | uint256 deposit; 31 | uint8 nonce; 32 | } 33 | 34 | // The requests from sender to close a channel 35 | mapping (bytes32 => ClosingRequest) public closingRequests; 36 | struct ClosingRequest { 37 | uint256 closingBalance; 38 | uint256 closeTime; 39 | } 40 | 41 | /* 42 | * Events 43 | */ 44 | 45 | event ChannelCreated( 46 | address indexed sender, 47 | address indexed receiver, 48 | uint8 indexed nonce, 49 | uint256 deposit 50 | ); 51 | event ChannelCloseRequested( 52 | address indexed sender, 53 | address indexed receiver, 54 | uint8 indexed nonce, 55 | uint256 balance 56 | ); 57 | event ChannelClosed( 58 | address indexed sender, 59 | address indexed receiver, 60 | uint8 indexed nonce, 61 | uint256 balance 62 | ); 63 | 64 | /** 65 | * @dev Constructor 66 | * @param tokenAddress address, the address of the ERC20 token that will be used 67 | * @param _challengeTime uint256, the time that a channel has before ends 68 | with and uncoopertive close 69 | */ 70 | function LifChannels( 71 | address tokenAddress, 72 | uint256 _challengeTime 73 | ) public { 74 | require(tokenAddress != address(0)); 75 | require(_challengeTime >= 0); 76 | 77 | token = ERC20(tokenAddress); 78 | challengeTime = _challengeTime; 79 | } 80 | 81 | /** 82 | * @dev Creates a channel between the msg.sender and the receiver 83 | * @param receiver address, the receiver of the channel 84 | * @param deposit uint256, the balance taht I want to load in the channel 85 | * @param nonce uint8, the nonce number of the channel 86 | */ 87 | function openChannel( 88 | address receiver, 89 | uint256 deposit, 90 | uint8 nonce 91 | ) external { 92 | 93 | require(nonce > 0); 94 | require(receiver != address(0)); 95 | require(deposit > 0); 96 | 97 | // Create unique identifier from sender, receiver and current block timestamp 98 | bytes32 channelId = getChannelId(msg.sender, receiver, nonce); 99 | 100 | // Check taht teh channel not exist 101 | require(channels[channelId].deposit == 0); 102 | require(channels[channelId].nonce == 0); 103 | require(closingRequests[channelId].closeTime == 0); 104 | 105 | // Store channel information 106 | channels[channelId] = Channel({deposit: deposit, nonce: nonce}); 107 | ChannelCreated(msg.sender, receiver, nonce, deposit); 108 | 109 | // transferFrom deposit from sender to contract 110 | // ! needs prior approval from user 111 | require(token.transferFrom(msg.sender, address(this), deposit)); 112 | } 113 | 114 | /** 115 | * @dev Starts a close channel request form the sender 116 | * @param receiver address, the receiver of the channel 117 | * @param nonce uint8, the nonce number of the channel 118 | * @param balance uint256, the final balance of teh receiver 119 | */ 120 | function uncooperativeClose( 121 | address receiver, 122 | uint8 nonce, 123 | uint256 balance 124 | ) external { 125 | bytes32 channelId = getChannelId(msg.sender, receiver, nonce); 126 | 127 | // Check that the closing request dont exist 128 | require(closingRequests[channelId].closeTime == 0); 129 | 130 | // Check that the balance is less that the deposited 131 | require(balance <= channels[channelId].deposit); 132 | 133 | // Mark channel as closed and create closing request 134 | closingRequests[channelId].closeTime = block.timestamp.add(challengeTime); 135 | require(closingRequests[channelId].closeTime > block.timestamp); 136 | closingRequests[channelId].closingBalance = balance; 137 | ChannelCloseRequested(msg.sender, receiver, nonce, balance); 138 | } 139 | 140 | /** 141 | * @dev Close a channel with the agreement of the sender and receiver 142 | * @param receiver address, the receiver of the channel 143 | * @param nonce uint8, the nonce number of the channel 144 | * @param balanceMsgSig bytes, the signature of the sender 145 | * @param closingSig bytes, the signature of the receiver 146 | */ 147 | function cooperativeClose( 148 | address receiver, 149 | uint8 nonce, 150 | uint256 balance, 151 | bytes balanceMsgSig, 152 | bytes closingSig 153 | ) external { 154 | // Derive receiver address from signature 155 | bytes32 msgHash = keccak256(balanceMsgSig); 156 | require(receiver == msgHash.recover(closingSig)); 157 | 158 | // Derive sender address from signed balance proof 159 | address sender = getSignerOfBalanceHash(receiver, nonce, balance, balanceMsgSig); 160 | 161 | close(sender, receiver, nonce, balance); 162 | } 163 | 164 | /** 165 | * @dev Close a channel with an existing closing request 166 | * @param receiver address, the receiver of the channel 167 | * @param nonce uint8, the nonce number of the channel 168 | */ 169 | function closeChannel(address receiver, uint8 nonce) external { 170 | bytes32 channelId = getChannelId(msg.sender, receiver, nonce); 171 | 172 | // Check that the closing request was created 173 | require(closingRequests[channelId].closeTime > 0); 174 | 175 | // Make sure the challengeTime has ended 176 | require(block.timestamp > closingRequests[channelId].closeTime); 177 | 178 | close(msg.sender, receiver, nonce, 179 | closingRequests[channelId].closingBalance 180 | ); 181 | } 182 | 183 | /** 184 | * @dev Get the channel info 185 | * @param sender address, the sender of the channel 186 | * @param receiver address, the receiver of the channel 187 | * @param nonce uint8, the nonce number of the channel 188 | */ 189 | function getChannelInfo( 190 | address sender, 191 | address receiver, 192 | uint8 nonce 193 | ) external view returns (bytes32, uint256, uint256, uint256) { 194 | bytes32 channelId = getChannelId(sender, receiver, nonce); 195 | require(channels[channelId].nonce > 0); 196 | 197 | return ( 198 | channelId, 199 | channels[channelId].deposit, 200 | closingRequests[channelId].closeTime, 201 | closingRequests[channelId].closingBalance 202 | ); 203 | } 204 | 205 | /* 206 | * Public functions 207 | */ 208 | 209 | /** 210 | * @dev Get the signer of a balance hash signed with a generated hash on chain 211 | * @param receiver address, the receiver to hash 212 | * @param nonce uint8, the nonce number of the channel 213 | * @param balance uint256, the balance to hash 214 | * @param msgSigned bytes, the balance hash signed 215 | */ 216 | function getSignerOfBalanceHash( 217 | address receiver, 218 | uint8 nonce, 219 | uint256 balance, 220 | bytes msgSigned 221 | ) public view returns (address) { 222 | bytes32 msgHash = generateBalanceHash(receiver, nonce, balance); 223 | // Derive address from signature 224 | address signer = msgHash.recover(msgSigned); 225 | return signer; 226 | } 227 | 228 | /** 229 | * @dev Generate a hash balance for an address 230 | * @param receiver address, the receiver to hash 231 | * @param nonce uint8, the nonce number of the channel 232 | * @param balance uint256, the balance to hash 233 | */ 234 | function generateBalanceHash( 235 | address receiver, 236 | uint8 nonce, 237 | uint256 balance 238 | ) public view returns (bytes32) { 239 | return keccak256(receiver, nonce, balance, address(this)); 240 | } 241 | 242 | /** 243 | * @dev Generate a keccak256 hash 244 | * @param message bytes, the mesage to hash 245 | */ 246 | function generateKeccak256(bytes message) public pure returns(bytes32) { 247 | return keccak256(message); 248 | } 249 | 250 | /** 251 | * @dev Generate a channel id 252 | * @param sender address, the sender in the channel 253 | * @param receiver address, the receiver in the channel 254 | * @param nonce uint8, the nonce number of the channel 255 | */ 256 | function getChannelId( 257 | address sender, 258 | address receiver, 259 | uint8 nonce 260 | ) public pure returns (bytes32 data) { 261 | return keccak256(sender, receiver, nonce); 262 | } 263 | 264 | /* 265 | * Internal functions 266 | */ 267 | 268 | /** 269 | * @dev Close a channel 270 | * @param sender address, the sender in the channel 271 | * @param receiver address, the receiver in the channel 272 | * @param nonce uint8, the nonce number of the channel 273 | * @param receiverBalance uint256, the final balance of the receiver 274 | */ 275 | function close( 276 | address sender, 277 | address receiver, 278 | uint8 nonce, 279 | uint256 receiverBalance 280 | ) internal { 281 | bytes32 channelId = getChannelId(sender, receiver, nonce); 282 | Channel memory channel = channels[channelId]; 283 | 284 | require(channel.nonce > 0); 285 | require(receiverBalance <= channel.deposit); 286 | 287 | // Remove closed channel structures 288 | // channel.nonce will become 0 289 | // Change state before transfer call 290 | delete channels[channelId]; 291 | delete closingRequests[channelId]; 292 | 293 | // Send balance to the receiver, as it is always <= deposit 294 | require(token.transfer(receiver, receiverBalance)); 295 | 296 | // Send deposit - balance back to sender 297 | require(token.transfer(sender, channel.deposit.sub(receiverBalance))); 298 | 299 | ChannelClosed(sender, receiver, nonce, receiverBalance); 300 | } 301 | 302 | } 303 | -------------------------------------------------------------------------------- /contracts/LifCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "zeppelin-solidity/contracts/lifecycle/Pausable.sol"; 5 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 6 | import "./LifToken.sol"; 7 | import "./VestedPayment.sol"; 8 | import "./LifMarketValidationMechanism.sol"; 9 | 10 | /** 11 | @title Crowdsale for the Lif Token Generation Event 12 | 13 | Implementation of the Lif Token Generation Event (TGE) Crowdsale: A 2 week 14 | fixed price, uncapped token sale, with a discounted ratefor contributions 15 | ìn the private presale and a Market Validation Mechanism that will receive 16 | the funds over the USD 10M soft cap. 17 | The crowdsale has a minimum cap of USD 5M which in case of not being reached 18 | by purchases made during the 2 week period the token will not start operating 19 | and all funds sent during that period will be made available to be claimed by 20 | the originating addresses. 21 | Funds up to the USD 10M soft cap will be sent to the Winding Tree Foundation 22 | wallet at the end of the crowdsale. 23 | Funds over that amount will be put in a MarketValidationMechanism (MVM) smart 24 | contract that guarantees a price floor for a period of 2 or 4 years, allowing 25 | any token holder to burn their tokens in exchange of part of the eth amount 26 | sent during the TGE in exchange of those tokens. 27 | */ 28 | contract LifCrowdsale is Ownable, Pausable { 29 | using SafeMath for uint256; 30 | 31 | // The token being sold. 32 | LifToken public token; 33 | 34 | // Beginning of the period where tokens can be purchased at rate `rate1`. 35 | uint256 public startTimestamp; 36 | // Moment after which the rate to buy tokens goes from `rate1` to `rate2`. 37 | uint256 public end1Timestamp; 38 | // Marks the end of the Token Generation Event. 39 | uint256 public end2Timestamp; 40 | 41 | // Address of the Winding Tree Foundation wallet. Funds up to the soft cap are 42 | // sent to this address. It's also the address to which the MVM distributes 43 | // the funds that are made available month after month. An extra 5% of tokens 44 | // are put in a Vested Payment with this address as beneficiary, acting as a 45 | // long-term reserve for the foundation. 46 | address public foundationWallet; 47 | 48 | // Address of the Winding Tree Founders wallet. An extra 12.8% of tokens 49 | // are put in a Vested Payment with this address as beneficiary, with 1 year 50 | // cliff and 4 years duration. 51 | address public foundersWallet; 52 | 53 | // TGE min cap, in USD. Converted to wei using `weiPerUSDinTGE`. 54 | uint256 public minCapUSD = 5000000; 55 | 56 | // Maximun amount from the TGE that the foundation receives, in USD. Converted 57 | // to wei using `weiPerUSDinTGE`. Funds over this cap go to the MVM. 58 | uint256 public maxFoundationCapUSD = 10000000; 59 | 60 | // Maximum amount from the TGE that makes the MVM to last for 24 months. If 61 | // funds from the TGE exceed this amount, the MVM will last for 24 months. 62 | uint256 public MVM24PeriodsCapUSD = 40000000; 63 | 64 | // Conversion rate from USD to wei to use during the TGE. 65 | uint256 public weiPerUSDinTGE = 0; 66 | 67 | // Seconds before the TGE since when the corresponding USD to 68 | // wei rate cannot be set by the owner anymore. 69 | uint256 public setWeiLockSeconds = 0; 70 | 71 | // Quantity of Lif that is received in exchage of 1 Ether during the first 72 | // week of the 2 weeks TGE 73 | uint256 public rate1; 74 | 75 | // Quantity of Lif that is received in exchage of 1 Ether during the second 76 | // week of the 2 weeks TGE 77 | uint256 public rate2; 78 | 79 | // Amount of wei received in exchange of tokens during the 2 weeks TGE 80 | uint256 public weiRaised; 81 | 82 | // Amount of lif minted and transferred during the TGE 83 | uint256 public tokensSold; 84 | 85 | // Address of the vesting schedule for the foundation created at the 86 | // end of the crowdsale 87 | VestedPayment public foundationVestedPayment; 88 | 89 | // Address of the vesting schedule for founders created at the 90 | // end of the crowdsale 91 | VestedPayment public foundersVestedPayment; 92 | 93 | // Address of the MVM created at the end of the crowdsale 94 | LifMarketValidationMechanism public MVM; 95 | 96 | // Tracks the wei sent per address during the 2 week TGE. This is the amount 97 | // that can be claimed by each address in case the minimum cap is not reached 98 | mapping(address => uint256) public purchases; 99 | 100 | // Has the Crowdsale been finalized by a successful call to `finalize`? 101 | bool public isFinalized = false; 102 | 103 | /** 104 | @dev Event triggered (at most once) on a successful call to `finalize` 105 | **/ 106 | event Finalized(); 107 | 108 | /** 109 | @dev Event triggered every time a presale purchase is done 110 | **/ 111 | event TokenPresalePurchase(address indexed beneficiary, uint256 weiAmount, uint256 rate); 112 | 113 | /** 114 | @dev Event triggered on every purchase during the TGE 115 | 116 | @param purchaser who paid for the tokens 117 | @param beneficiary who got the tokens 118 | @param value amount of wei paid 119 | @param amount amount of tokens purchased 120 | */ 121 | event TokenPurchase( 122 | address indexed purchaser, 123 | address indexed beneficiary, 124 | uint256 value, 125 | uint256 amount 126 | ); 127 | 128 | /** 129 | @dev Constructor. Creates the token in a paused state 130 | 131 | @param _startTimestamp see `startTimestamp` 132 | @param _end1Timestamp see `end1Timestamp` 133 | @param _end2Timestamp see `end2Timestamp 134 | @param _rate1 see `rate1` 135 | @param _rate2 see `rate2` 136 | @param _foundationWallet see `foundationWallet` 137 | */ 138 | function LifCrowdsale( 139 | uint256 _startTimestamp, 140 | uint256 _end1Timestamp, 141 | uint256 _end2Timestamp, 142 | uint256 _rate1, 143 | uint256 _rate2, 144 | uint256 _setWeiLockSeconds, 145 | address _foundationWallet, 146 | address _foundersWallet 147 | ) { 148 | 149 | require(_startTimestamp > block.timestamp); 150 | require(_end1Timestamp > _startTimestamp); 151 | require(_end2Timestamp > _end1Timestamp); 152 | require(_rate1 > 0); 153 | require(_rate2 > 0); 154 | require(_setWeiLockSeconds > 0); 155 | require(_foundationWallet != address(0)); 156 | require(_foundersWallet != address(0)); 157 | 158 | token = new LifToken(); 159 | token.pause(); 160 | 161 | startTimestamp = _startTimestamp; 162 | end1Timestamp = _end1Timestamp; 163 | end2Timestamp = _end2Timestamp; 164 | rate1 = _rate1; 165 | rate2 = _rate2; 166 | setWeiLockSeconds = _setWeiLockSeconds; 167 | foundationWallet = _foundationWallet; 168 | foundersWallet = _foundersWallet; 169 | } 170 | 171 | /** 172 | @dev Set the wei per USD rate for the TGE. Has to be called by 173 | the owner up to `setWeiLockSeconds` before `startTimestamp` 174 | 175 | @param _weiPerUSD wei per USD rate valid during the TGE 176 | */ 177 | function setWeiPerUSDinTGE(uint256 _weiPerUSD) public onlyOwner { 178 | require(_weiPerUSD > 0); 179 | assert(block.timestamp < startTimestamp.sub(setWeiLockSeconds)); 180 | 181 | weiPerUSDinTGE = _weiPerUSD; 182 | } 183 | 184 | /** 185 | @dev Returns the current Lif per Eth rate during the TGE 186 | 187 | @return the current Lif per Eth rate or 0 when not in TGE 188 | */ 189 | function getRate() public view returns (uint256) { 190 | if (block.timestamp < startTimestamp) 191 | return 0; 192 | else if (block.timestamp <= end1Timestamp) 193 | return rate1; 194 | else if (block.timestamp <= end2Timestamp) 195 | return rate2; 196 | else 197 | return 0; 198 | } 199 | 200 | /** 201 | @dev Fallback function, payable. Calls `buyTokens` 202 | */ 203 | function () payable { 204 | buyTokens(msg.sender); 205 | } 206 | 207 | /** 208 | @dev Allows to get tokens during the TGE. Payable. The value is converted to 209 | Lif using the current rate obtained by calling `getRate()`. 210 | 211 | @param beneficiary Address to which Lif should be sent 212 | */ 213 | function buyTokens(address beneficiary) public payable whenNotPaused validPurchase { 214 | require(beneficiary != address(0)); 215 | assert(weiPerUSDinTGE > 0); 216 | 217 | uint256 weiAmount = msg.value; 218 | 219 | // get current price (it depends on current block number) 220 | uint256 rate = getRate(); 221 | 222 | assert(rate > 0); 223 | 224 | // calculate token amount to be created 225 | uint256 tokens = weiAmount.mul(rate); 226 | 227 | // store wei amount in case of TGE min cap not reached 228 | weiRaised = weiRaised.add(weiAmount); 229 | purchases[beneficiary] = purchases[beneficiary].add(weiAmount); 230 | tokensSold = tokensSold.add(tokens); 231 | 232 | token.mint(beneficiary, tokens); 233 | TokenPurchase(msg.sender, beneficiary, weiAmount, tokens); 234 | } 235 | 236 | /** 237 | @dev Allows to add the address and the amount of wei sent by a contributor 238 | in the private presale. Can only be called by the owner before the beginning 239 | of TGE 240 | 241 | @param beneficiary Address to which Lif will be sent 242 | @param weiSent Amount of wei contributed 243 | @param rate Lif per ether rate at the moment of the contribution 244 | */ 245 | function addPrivatePresaleTokens( 246 | address beneficiary, uint256 weiSent, uint256 rate 247 | ) public onlyOwner { 248 | require(block.timestamp < startTimestamp); 249 | require(beneficiary != address(0)); 250 | require(weiSent > 0); 251 | 252 | // validate that rate is higher than TGE rate 253 | require(rate > rate1); 254 | 255 | uint256 tokens = weiSent.mul(rate); 256 | 257 | weiRaised = weiRaised.add(weiSent); 258 | 259 | token.mint(beneficiary, tokens); 260 | 261 | TokenPresalePurchase(beneficiary, weiSent, rate); 262 | } 263 | 264 | /** 265 | @dev Internal. Forwards funds to the foundation wallet and in case the soft 266 | cap was exceeded it also creates and funds the Market Validation Mechanism. 267 | */ 268 | function forwardFunds(bool deployMVM) internal { 269 | 270 | // calculate the max amount of wei for the foundation 271 | uint256 foundationBalanceCapWei = maxFoundationCapUSD.mul(weiPerUSDinTGE); 272 | 273 | // If the minimiun cap for the MVM is not reached or the MVM cant be deployed 274 | // transfer all funds to foundation else if the min cap for the MVM is reached, 275 | // create it and send the remaining funds. 276 | // We use weiRaised to compare becuase that is the total amount of wei raised in all TGE 277 | // but we have to distribute the balance using `this.balance` because thats the amount 278 | // raised by the crowdsale 279 | if ((weiRaised <= foundationBalanceCapWei) || !deployMVM) { 280 | 281 | foundationWallet.transfer(this.balance); 282 | 283 | mintExtraTokens(uint256(24)); 284 | 285 | } else { 286 | 287 | uint256 mmFundBalance = this.balance.sub(foundationBalanceCapWei); 288 | 289 | // check how much preiods we have to use on the MVM 290 | uint8 MVMPeriods = 24; 291 | if (mmFundBalance > MVM24PeriodsCapUSD.mul(weiPerUSDinTGE)) 292 | MVMPeriods = 48; 293 | 294 | foundationWallet.transfer(foundationBalanceCapWei); 295 | 296 | MVM = new LifMarketValidationMechanism( 297 | address(token), block.timestamp.add(30 days), 30 days, MVMPeriods, foundationWallet 298 | ); 299 | MVM.calculateDistributionPeriods(); 300 | 301 | mintExtraTokens(uint256(MVMPeriods)); 302 | 303 | MVM.fund.value(mmFundBalance)(); 304 | MVM.transferOwnership(foundationWallet); 305 | 306 | } 307 | } 308 | 309 | /** 310 | @dev Internal. Distribute extra tokens among founders, 311 | team and the foundation long-term reserve. Founders receive 312 | 12.8% of tokens in a 4y (1y cliff) vesting schedule. 313 | Foundation long-term reserve receives 5% of tokens in a 314 | vesting schedule with the same duration as the MVM that 315 | starts when the MVM ends. An extra 7.2% is transferred to 316 | the foundation to be distributed among advisors and future hires 317 | */ 318 | function mintExtraTokens(uint256 foundationMonthsStart) internal { 319 | // calculate how much tokens will the founders, 320 | // foundation and advisors will receive 321 | uint256 foundersTokens = token.totalSupply().mul(128).div(1000); 322 | uint256 foundationTokens = token.totalSupply().mul(50).div(1000); 323 | uint256 teamTokens = token.totalSupply().mul(72).div(1000); 324 | 325 | // create the vested payment schedule for the founders 326 | foundersVestedPayment = new VestedPayment( 327 | block.timestamp, 30 days, 48, 12, foundersTokens, token 328 | ); 329 | token.mint(foundersVestedPayment, foundersTokens); 330 | foundersVestedPayment.transferOwnership(foundersWallet); 331 | 332 | // create the vested payment schedule for the foundation 333 | uint256 foundationPaymentStart = foundationMonthsStart.mul(30 days) 334 | .add(30 days); 335 | foundationVestedPayment = new VestedPayment( 336 | block.timestamp.add(foundationPaymentStart), 30 days, 337 | foundationMonthsStart, 0, foundationTokens, token 338 | ); 339 | token.mint(foundationVestedPayment, foundationTokens); 340 | foundationVestedPayment.transferOwnership(foundationWallet); 341 | 342 | // transfer the token for advisors and future employees to the foundation 343 | token.mint(foundationWallet, teamTokens); 344 | 345 | } 346 | 347 | /** 348 | @dev Modifier 349 | ok if the transaction can buy tokens on TGE 350 | */ 351 | modifier validPurchase() { 352 | bool withinPeriod = now >= startTimestamp && now <= end2Timestamp; 353 | bool nonZeroPurchase = msg.value != 0; 354 | assert(withinPeriod && nonZeroPurchase); 355 | _; 356 | } 357 | 358 | /** 359 | @dev Modifier 360 | ok when block.timestamp is past end2Timestamp 361 | */ 362 | modifier hasEnded() { 363 | assert(block.timestamp > end2Timestamp); 364 | _; 365 | } 366 | 367 | /** 368 | @dev Modifier 369 | @return true if minCapUSD has been reached by contributions during the TGE 370 | */ 371 | function funded() public view returns (bool) { 372 | assert(weiPerUSDinTGE > 0); 373 | return weiRaised >= minCapUSD.mul(weiPerUSDinTGE); 374 | } 375 | 376 | /** 377 | @dev Allows a TGE contributor to claim their contributed eth in case the 378 | TGE has finished without reaching the minCapUSD 379 | */ 380 | function claimEth() public whenNotPaused hasEnded { 381 | require(isFinalized); 382 | require(!funded()); 383 | 384 | uint256 toReturn = purchases[msg.sender]; 385 | assert(toReturn > 0); 386 | 387 | purchases[msg.sender] = 0; 388 | 389 | msg.sender.transfer(toReturn); 390 | } 391 | 392 | /** 393 | @dev Allows the owner to return an purchase to a contributor 394 | */ 395 | function returnPurchase(address contributor) 396 | public hasEnded onlyOwner 397 | { 398 | require(!isFinalized); 399 | 400 | uint256 toReturn = purchases[contributor]; 401 | assert(toReturn > 0); 402 | 403 | uint256 tokenBalance = token.balanceOf(contributor); 404 | 405 | // Substract weiRaised and tokens sold 406 | weiRaised = weiRaised.sub(toReturn); 407 | tokensSold = tokensSold.sub(tokenBalance); 408 | token.burn(contributor, tokenBalance); 409 | purchases[contributor] = 0; 410 | 411 | contributor.transfer(toReturn); 412 | } 413 | 414 | /** 415 | @dev Finalizes the crowdsale, taking care of transfer of funds to the 416 | Winding Tree Foundation and creation and funding of the Market Validation 417 | Mechanism in case the soft cap was exceeded. It also unpauses the token to 418 | enable transfers. It can be called only once, after `end2Timestamp` 419 | */ 420 | function finalize(bool deployMVM) public onlyOwner hasEnded { 421 | require(!isFinalized); 422 | 423 | // foward founds and unpause token only if minCap is reached 424 | if (funded()) { 425 | 426 | forwardFunds(deployMVM); 427 | 428 | // finish the minting of the token 429 | token.finishMinting(); 430 | 431 | // transfer the ownership of the token to the foundation 432 | token.transferOwnership(owner); 433 | 434 | } 435 | 436 | Finalized(); 437 | isFinalized = true; 438 | } 439 | 440 | } 441 | -------------------------------------------------------------------------------- /contracts/LifMarketValidationMechanism.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 5 | import "./LifToken.sol"; 6 | 7 | /** 8 | @title Market Validation Mechanism (MVM) 9 | */ 10 | contract LifMarketValidationMechanism is Ownable { 11 | using SafeMath for uint256; 12 | 13 | // The Lif token contract 14 | LifToken public lifToken; 15 | 16 | // The address of the foundation wallet. It can claim part of the eth funds 17 | // following an exponential curve until the end of the MVM lifetime (24 or 48 18 | // months). After that it can claim 100% of the remaining eth in the MVM. 19 | address public foundationAddr; 20 | 21 | // The amount of wei that the MVM received initially 22 | uint256 public initialWei; 23 | 24 | // Start timestamp since which the MVM begins to accept tokens via sendTokens 25 | uint256 public startTimestamp; 26 | 27 | // Quantity of seconds in every period, usually equivalent to 30 days 28 | uint256 public secondsPerPeriod; 29 | 30 | // Number of periods. It should be 24 or 48 (each period is roughly a month) 31 | uint8 public totalPeriods; 32 | 33 | // The total amount of wei that was claimed by the foundation so far 34 | uint256 public totalWeiClaimed = 0; 35 | 36 | // The price at which the MVM buys tokens at the beginning of its lifetime 37 | uint256 public initialBuyPrice = 0; 38 | 39 | // Amount of tokens that were burned by the MVM 40 | uint256 public totalBurnedTokens = 0; 41 | 42 | // Amount of wei that was reimbursed via sendTokens calls 43 | uint256 public totalReimbursedWei = 0; 44 | 45 | // Total supply of tokens when the MVM was created 46 | uint256 public originalTotalSupply; 47 | 48 | uint256 constant PRICE_FACTOR = 100000; 49 | 50 | // Has the MVM been funded by calling `fund`? It can be funded only once 51 | bool public funded = false; 52 | 53 | // true when the market MVM is paused 54 | bool public paused = false; 55 | 56 | // total amount of seconds that the MVM was paused 57 | uint256 public totalPausedSeconds = 0; 58 | 59 | // the timestamp where the MVM was paused 60 | uint256 public pausedTimestamp; 61 | 62 | uint256[] public periods; 63 | 64 | // Events 65 | event Pause(); 66 | event Unpause(uint256 pausedSeconds); 67 | 68 | event ClaimedWei(uint256 claimedWei); 69 | event SentTokens(address indexed sender, uint256 price, uint256 tokens, uint256 returnedWei); 70 | 71 | modifier whenNotPaused(){ 72 | assert(!paused); 73 | _; 74 | } 75 | 76 | modifier whenPaused(){ 77 | assert(paused); 78 | _; 79 | } 80 | 81 | /** 82 | @dev Constructor 83 | 84 | @param lifAddr the lif token address 85 | @param _startTimestamp see `startTimestamp` 86 | @param _secondsPerPeriod see `secondsPerPeriod` 87 | @param _totalPeriods see `totalPeriods` 88 | @param _foundationAddr see `foundationAddr` 89 | */ 90 | function LifMarketValidationMechanism( 91 | address lifAddr, uint256 _startTimestamp, uint256 _secondsPerPeriod, 92 | uint8 _totalPeriods, address _foundationAddr 93 | ) { 94 | require(lifAddr != address(0)); 95 | require(_startTimestamp > block.timestamp); 96 | require(_secondsPerPeriod > 0); 97 | require(_totalPeriods == 24 || _totalPeriods == 48); 98 | require(_foundationAddr != address(0)); 99 | 100 | lifToken = LifToken(lifAddr); 101 | startTimestamp = _startTimestamp; 102 | secondsPerPeriod = _secondsPerPeriod; 103 | totalPeriods = _totalPeriods; 104 | foundationAddr = _foundationAddr; 105 | 106 | } 107 | 108 | /** 109 | @dev Receives the initial funding from the Crowdsale. Calculates the 110 | initial buy price as initialWei / totalSupply 111 | */ 112 | function fund() public payable onlyOwner { 113 | assert(!funded); 114 | 115 | originalTotalSupply = lifToken.totalSupply(); 116 | initialWei = msg.value; 117 | initialBuyPrice = initialWei. 118 | mul(PRICE_FACTOR). 119 | div(originalTotalSupply); 120 | 121 | funded = true; 122 | } 123 | 124 | /** 125 | @dev Change the LifToken address 126 | */ 127 | function changeToken(address newToken) public onlyOwner { 128 | lifToken = LifToken(newToken); 129 | } 130 | 131 | /** 132 | @dev calculates the exponential distribution curve. It determines how much 133 | wei can be distributed back to the foundation every month. It starts with 134 | very low amounts ending with higher chunks at the end of the MVM lifetime 135 | */ 136 | function calculateDistributionPeriods() public { 137 | assert(totalPeriods == 24 || totalPeriods == 48); 138 | assert(periods.length == 0); 139 | 140 | // Table with the max delta % that can be distributed back to the foundation on 141 | // each period. It follows an exponential curve (starts with lower % and ends 142 | // with higher %) to keep the funds in the MVM longer. deltas24 143 | // is used when MVM lifetime is 24 months, deltas48 when it's 48 months. 144 | // The sum is less than 100% because the last % is missing: after the last period 145 | // the 100% remaining can be claimed by the foundation. Values multipled by 10^5 146 | 147 | uint256[24] memory accumDistribution24 = [ 148 | uint256(0), 18, 117, 351, 767, 1407, 149 | 2309, 3511, 5047, 6952, 9257, 11995, 150 | 15196, 18889, 23104, 27870, 33215, 39166, 151 | 45749, 52992, 60921, 69561, 78938, 89076 152 | ]; 153 | 154 | uint256[48] memory accumDistribution48 = [ 155 | uint256(0), 3, 18, 54, 117, 214, 351, 534, 156 | 767, 1056, 1406, 1822, 2308, 2869, 3510, 4234, 157 | 5046, 5950, 6950, 8051, 9256, 10569, 11994, 13535, 158 | 15195, 16978, 18888, 20929, 23104, 25416, 27870, 30468, 159 | 33214, 36112, 39165, 42376, 45749, 49286, 52992, 56869, 160 | 60921, 65150, 69560, 74155, 78937, 83909, 89075, 94438 161 | ]; 162 | 163 | for (uint8 i = 0; i < totalPeriods; i++) { 164 | 165 | if (totalPeriods == 24) { 166 | periods.push(accumDistribution24[i]); 167 | } else { 168 | periods.push(accumDistribution48[i]); 169 | } 170 | 171 | } 172 | } 173 | 174 | /** 175 | @dev Returns the current period as a number from 0 to totalPeriods 176 | 177 | @return the current period as a number from 0 to totalPeriods 178 | */ 179 | function getCurrentPeriodIndex() public view returns(uint256) { 180 | assert(block.timestamp >= startTimestamp); 181 | return block.timestamp.sub(startTimestamp). 182 | sub(totalPausedSeconds). 183 | div(secondsPerPeriod); 184 | } 185 | 186 | /** 187 | @dev calculates the accumulated distribution percentage as of now, 188 | following the exponential distribution curve 189 | 190 | @return the accumulated distribution percentage, used to calculate things 191 | like the maximum amount that can be claimed by the foundation 192 | */ 193 | function getAccumulatedDistributionPercentage() public view returns(uint256 percentage) { 194 | uint256 period = getCurrentPeriodIndex(); 195 | 196 | assert(period < totalPeriods); 197 | 198 | return periods[period]; 199 | } 200 | 201 | /** 202 | @dev returns the current buy price at which the MVM offers to buy tokens to 203 | burn them 204 | 205 | @return the current buy price (in eth/lif, multiplied by PRICE_FACTOR) 206 | */ 207 | function getBuyPrice() public view returns (uint256 price) { 208 | uint256 accumulatedDistributionPercentage = getAccumulatedDistributionPercentage(); 209 | 210 | return initialBuyPrice. 211 | mul(PRICE_FACTOR.sub(accumulatedDistributionPercentage)). 212 | div(PRICE_FACTOR); 213 | } 214 | 215 | /** 216 | @dev Returns the maximum amount of wei that the foundation can claim. It's 217 | a portion of the ETH that was not claimed by token holders 218 | 219 | @return the maximum wei claimable by the foundation as of now 220 | */ 221 | function getMaxClaimableWeiAmount() public view returns (uint256) { 222 | if (isFinished()) { 223 | return this.balance; 224 | } else { 225 | uint256 claimableFromReimbursed = initialBuyPrice. 226 | mul(totalBurnedTokens).div(PRICE_FACTOR). 227 | sub(totalReimbursedWei); 228 | uint256 currentCirculation = lifToken.totalSupply(); 229 | uint256 accumulatedDistributionPercentage = getAccumulatedDistributionPercentage(); 230 | uint256 maxClaimable = initialWei. 231 | mul(accumulatedDistributionPercentage).div(PRICE_FACTOR). 232 | mul(currentCirculation).div(originalTotalSupply). 233 | add(claimableFromReimbursed); 234 | 235 | if (maxClaimable > totalWeiClaimed) { 236 | return maxClaimable.sub(totalWeiClaimed); 237 | } else { 238 | return 0; 239 | } 240 | } 241 | } 242 | 243 | /** 244 | @dev allows to send tokens to the MVM in exchange of Eth at the price 245 | determined by getBuyPrice. The tokens are burned 246 | */ 247 | function sendTokens(uint256 tokens) public whenNotPaused { 248 | require(tokens > 0); 249 | 250 | uint256 price = getBuyPrice(); 251 | uint256 totalWei = tokens.mul(price).div(PRICE_FACTOR); 252 | 253 | lifToken.transferFrom(msg.sender, address(this), tokens); 254 | lifToken.burn(tokens); 255 | totalBurnedTokens = totalBurnedTokens.add(tokens); 256 | 257 | SentTokens(msg.sender, price, tokens, totalWei); 258 | 259 | totalReimbursedWei = totalReimbursedWei.add(totalWei); 260 | msg.sender.transfer(totalWei); 261 | } 262 | 263 | /** 264 | @dev Returns whether the MVM end-of-life has been reached. When that 265 | happens no more tokens can be sent to the MVM and the foundation can claim 266 | 100% of the remaining balance in the MVM 267 | 268 | @return true if the MVM end-of-life has been reached 269 | */ 270 | function isFinished() public view returns (bool finished) { 271 | return getCurrentPeriodIndex() >= totalPeriods; 272 | } 273 | 274 | /** 275 | @dev Called from the foundation wallet to claim eth back from the MVM. 276 | Maximum amount that can be claimed is determined by 277 | getMaxClaimableWeiAmount 278 | */ 279 | function claimWei(uint256 weiAmount) public whenNotPaused { 280 | require(msg.sender == foundationAddr); 281 | 282 | uint256 claimable = getMaxClaimableWeiAmount(); 283 | 284 | assert(claimable >= weiAmount); 285 | 286 | foundationAddr.transfer(weiAmount); 287 | 288 | totalWeiClaimed = totalWeiClaimed.add(weiAmount); 289 | 290 | ClaimedWei(weiAmount); 291 | } 292 | 293 | /** 294 | @dev Pauses the MVM. No tokens can be sent to the MVM and no eth can be 295 | claimed from the MVM while paused. MVM total lifetime is extended by the 296 | period it stays paused 297 | */ 298 | function pause() public onlyOwner whenNotPaused { 299 | paused = true; 300 | pausedTimestamp = block.timestamp; 301 | 302 | Pause(); 303 | } 304 | 305 | /** 306 | @dev Unpauses the MVM. See `pause` for more details about pausing 307 | */ 308 | function unpause() public onlyOwner whenPaused { 309 | uint256 pausedSeconds = block.timestamp.sub(pausedTimestamp); 310 | totalPausedSeconds = totalPausedSeconds.add(pausedSeconds); 311 | paused = false; 312 | 313 | Unpause(pausedSeconds); 314 | } 315 | 316 | } 317 | -------------------------------------------------------------------------------- /contracts/LifToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/token/ERC827/ERC827Token.sol"; 4 | import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol"; 5 | import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; 6 | import "zeppelin-solidity/contracts/token/ERC20/PausableToken.sol"; 7 | 8 | /** 9 | @title Líf, the Winding Tree token 10 | 11 | Implementation of Líf, the ERC827 token for Winding Tree, an extension of the 12 | ERC20 token with extra methods to transfer value and data to execute a call 13 | on transfer. 14 | Uses OpenZeppelin StandardToken, ERC827Token, MintableToken and PausableToken. 15 | */ 16 | contract LifToken is StandardToken, ERC827Token, MintableToken, PausableToken { 17 | // Token Name 18 | string public constant NAME = "Líf"; 19 | 20 | // Token Symbol 21 | string public constant SYMBOL = "LIF"; 22 | 23 | // Token decimals 24 | uint public constant DECIMALS = 18; 25 | 26 | /** 27 | * @dev Burns a specific amount of tokens. 28 | * 29 | * @param _value The amount of tokens to be burned. 30 | */ 31 | function burn(uint256 _value) public whenNotPaused { 32 | 33 | require(_value <= balances[msg.sender]); 34 | 35 | balances[msg.sender] = balances[msg.sender].sub(_value); 36 | totalSupply_ = totalSupply_.sub(_value); 37 | 38 | // a Transfer event to 0x0 can be useful for observers to keep track of 39 | // all the Lif by just looking at those events 40 | Transfer(msg.sender, address(0), _value); 41 | } 42 | 43 | /** 44 | * @dev Burns a specific amount of tokens of an address 45 | * This function can be called only by the owner in the minting process 46 | * 47 | * @param _value The amount of tokens to be burned. 48 | */ 49 | function burn(address burner, uint256 _value) public onlyOwner { 50 | 51 | require(!mintingFinished); 52 | 53 | require(_value <= balances[burner]); 54 | 55 | balances[burner] = balances[burner].sub(_value); 56 | totalSupply_ = totalSupply_.sub(_value); 57 | 58 | // a Transfer event to 0x0 can be useful for observers to keep track of 59 | // all the Lif by just looking at those events 60 | Transfer(burner, address(0), _value); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/LifTokenTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/token/ERC827/ERC827Token.sol"; 4 | import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol"; 5 | import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; 6 | import "zeppelin-solidity/contracts/token/ERC20/BurnableToken.sol"; 7 | import "zeppelin-solidity/contracts/token/ERC20/PausableToken.sol"; 8 | 9 | /** 10 | @title Líf, the Winding Tree token 11 | 12 | Implementation of Líf, the ERC827 token for Winding Tree, an extension of the 13 | ERC20 token with extra methods to transfer value and data to execute a call 14 | on transfer. 15 | This version of the token is used in test networks, it allows anyone to claim 16 | tokens. 17 | Uses OpenZeppelin StandardToken, ERC827Token, BurnableToken, MintableToken and PausableToken. 18 | */ 19 | contract LifTokenTest is StandardToken, ERC827Token, BurnableToken, MintableToken, PausableToken { 20 | // Token Name 21 | string public constant NAME = "Líf"; 22 | 23 | // Token Symbol 24 | string public constant SYMBOL = "LIF"; 25 | 26 | // Token decimals 27 | uint public constant DECIMALS = 18; 28 | 29 | // Max Lif faucet (50 tokens) 30 | uint256 public constant MAX_LIF_FAUCET = 50000000000000000000; 31 | 32 | function approveData(address spender, uint256 value, bytes data) public whenNotPaused returns (bool) { 33 | return super.approve(spender, value, data); 34 | } 35 | 36 | function transferData(address to, uint256 value, bytes data) public whenNotPaused returns (bool) { 37 | return super.transfer(to, value, data); 38 | } 39 | 40 | function transferDataFrom(address from, address to, uint256 value, bytes data) public whenNotPaused returns (bool) { 41 | return super.transferFrom(from, to, value, data); 42 | } 43 | 44 | /** 45 | * @dev Function to create tokens, it will issue tokens to the tx sender 46 | */ 47 | function faucetLif() public { 48 | uint256 amount = MAX_LIF_FAUCET.sub(balances[msg.sender]); 49 | totalSupply_ = totalSupply_.add(amount); 50 | balances[msg.sender] = balances[msg.sender].add(amount); 51 | Transfer(0x0, msg.sender, amount); 52 | } 53 | 54 | /** 55 | * @dev Burns a specific amount of tokens. 56 | * 57 | * @param _value The amount of tokens to be burned. 58 | */ 59 | function burn(uint256 _value) public whenNotPaused { 60 | super.burn(_value); 61 | 62 | // a Transfer event to 0x0 can be useful for observers to keep track of 63 | // all the Lif by just looking at those events 64 | Transfer(msg.sender, address(0), _value); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | contract Migrations { 4 | address public owner; 5 | 6 | // A function with the signature `last_completed_migration()`, returning a uint, is required. 7 | uint public last_completed_migration; 8 | 9 | modifier restricted() { 10 | if (msg.sender == owner) _; 11 | } 12 | 13 | function Migrations() { 14 | owner = msg.sender; 15 | } 16 | 17 | // A function with the signature `setCompleted(uint)` is required. 18 | function setCompleted(uint completed) restricted { 19 | last_completed_migration = completed; 20 | } 21 | 22 | function upgrade(address new_address) restricted { 23 | Migrations upgraded = Migrations(new_address); 24 | upgraded.setCompleted(last_completed_migration); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/VestedPayment.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 5 | import "./LifToken.sol"; 6 | 7 | /** 8 | @title Vested Payment Schedule for LifToken 9 | 10 | An ownable vesting schedule for the LifToken, the tokens can only be 11 | claimed by the owner. The contract has a start timestamp, a duration 12 | of each period in seconds (it can be days, months, years), a total 13 | amount of periods and a cliff. The available amount of tokens will 14 | be calculated based on the balance of LifTokens of the contract at 15 | that time. 16 | */ 17 | 18 | contract VestedPayment is Ownable { 19 | using SafeMath for uint256; 20 | 21 | // When the vested schedule starts 22 | uint256 public startTimestamp; 23 | 24 | // How many seconds each period will last 25 | uint256 public secondsPerPeriod; 26 | 27 | // How many periods will have in total 28 | uint256 public totalPeriods; 29 | 30 | // The amount of tokens to be vested in total 31 | uint256 public tokens; 32 | 33 | // How many tokens were claimed 34 | uint256 public claimed; 35 | 36 | // The token contract 37 | LifToken public token; 38 | 39 | // Duration (in periods) of the initial cliff in the vesting schedule 40 | uint256 public cliffDuration; 41 | 42 | /** 43 | @dev Constructor. 44 | 45 | @param _startTimestamp see `startTimestamp` 46 | @param _secondsPerPeriod see `secondsPerPeriod` 47 | @param _totalPeriods see `totalPeriods 48 | @param _cliffDuration see `cliffDuration` 49 | @param _tokens see `tokens` 50 | @param tokenAddress the address of the token contract 51 | */ 52 | function VestedPayment( 53 | uint256 _startTimestamp, uint256 _secondsPerPeriod, 54 | uint256 _totalPeriods, uint256 _cliffDuration, 55 | uint256 _tokens, address tokenAddress 56 | ) { 57 | require(_startTimestamp >= block.timestamp); 58 | require(_secondsPerPeriod > 0); 59 | require(_totalPeriods > 0); 60 | require(tokenAddress != address(0)); 61 | require(_cliffDuration < _totalPeriods); 62 | require(_tokens > 0); 63 | 64 | startTimestamp = _startTimestamp; 65 | secondsPerPeriod = _secondsPerPeriod; 66 | totalPeriods = _totalPeriods; 67 | cliffDuration = _cliffDuration; 68 | tokens = _tokens; 69 | token = LifToken(tokenAddress); 70 | } 71 | 72 | /** 73 | @dev Change the LifToken address 74 | */ 75 | function changeToken(address newToken) public onlyOwner { 76 | token = LifToken(newToken); 77 | } 78 | 79 | /** 80 | @dev Get how many tokens are available to be claimed 81 | */ 82 | function getAvailableTokens() public view returns (uint256) { 83 | uint256 period = block.timestamp.sub(startTimestamp) 84 | .div(secondsPerPeriod); 85 | 86 | if (period < cliffDuration) { 87 | return 0; 88 | } else if (period >= totalPeriods) { 89 | return tokens.sub(claimed); 90 | } else { 91 | return tokens.mul(period.add(1)).div(totalPeriods).sub(claimed); 92 | } 93 | } 94 | 95 | /** 96 | @dev Claim the tokens, they can be claimed only by the owner 97 | of the contract 98 | 99 | @param amount how many tokens to be claimed 100 | */ 101 | function claimTokens(uint256 amount) public onlyOwner { 102 | assert(getAvailableTokens() >= amount); 103 | 104 | claimed = claimed.add(amount); 105 | token.transfer(owner, amount); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /contracts/deploy/TGEDeployer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "../LifCrowdsale.sol"; 4 | 5 | /** 6 | @title TGEDeployer, A deployer contract for the Winding Tree TGE 7 | 8 | This contract is used to create a crowdsale and issue presale tokens in batches 9 | it will also set the weiPerUSD and transfer ownership, after that everything is 10 | ready for the TGE to succed. 11 | */ 12 | contract TGEDeployer { 13 | 14 | LifCrowdsale public crowdsale; 15 | address public wallet; 16 | address public owner; 17 | 18 | function TGEDeployer( 19 | uint256 startTimestamp, 20 | uint256 end1Timestamp, 21 | uint256 end2Timestamp, 22 | uint256 rate1, 23 | uint256 rate2, 24 | uint256 setWeiLockSeconds, 25 | address foundationWallet, 26 | address foundersWallet 27 | ) public { 28 | crowdsale = new LifCrowdsale( 29 | startTimestamp, end1Timestamp, end2Timestamp, rate1, rate2, 30 | setWeiLockSeconds, foundationWallet, foundersWallet 31 | ); 32 | wallet = foundationWallet; 33 | owner = msg.sender; 34 | } 35 | 36 | // Mint a batch of presale tokens 37 | function addPresaleTokens(address[] contributors, uint256[] values, uint256 rate) public { 38 | require(msg.sender == owner); 39 | require(contributors.length == values.length); 40 | for (uint32 i = 0; i < contributors.length; i ++) { 41 | crowdsale.addPrivatePresaleTokens(contributors[i], values[i], rate); 42 | } 43 | } 44 | 45 | // Set the wei per USD in the crowdsale and then transfer ownership to foundation 46 | function finish(uint256 weiPerUSDinTGE) public { 47 | require(msg.sender == owner); 48 | crowdsale.setWeiPerUSDinTGE(weiPerUSDinTGE); 49 | crowdsale.transferOwnership(wallet); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /contracts/test-helpers/Message.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract Message { 4 | 5 | event Show(bytes32 b32, uint256 number, string text); 6 | 7 | function showMessage(bytes32 _message, uint256 _number, string _text) public view { 8 | 9 | Show(_message, _number, _text); 10 | 11 | } 12 | 13 | function fail() public { 14 | 15 | revert(); 16 | 17 | } 18 | 19 | function call(address to, bytes data) public returns (bool) { 20 | if (to.call(data)) 21 | return true; 22 | else 23 | return false; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const LifChannelsContract = require('./build/contracts/LifChannels.json'); 2 | const LifCrowdsaleContract = require('./build/contracts/LifCrowdsale.json'); 3 | const LifMarketValidationMechanismContract = require('./build/contracts/LifMarketValidationMechanism.json'); 4 | const LifTokenContract = require('./build/contracts/LifToken.json'); 5 | const VestedPaymentContract = require('./build/contracts/VestedPayment.json'); 6 | 7 | module.exports = { 8 | LifChannelsContract: LifChannelsContract, 9 | LifCrowdsaleContract: LifCrowdsaleContract, 10 | LifMarketValidationMechanismContract: LifMarketValidationMechanismContract, 11 | LifTokenContract: LifTokenContract, 12 | VestedPaymentContract: VestedPaymentContract, 13 | } -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(deployer) { 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@windingtree/lif-token", 3 | "version": "0.1.0", 4 | "description": "Winding Tree Platform Token", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "scripts/test.sh", 8 | "coverage": "scripts/coverage.sh", 9 | "coveralls": "scripts/coveralls.sh", 10 | "lint": "eslint test", 11 | "clean": "rimraf build", 12 | "buildfornpm": "npm run clean && truffle compile" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/windingtree/LifToken" 20 | }, 21 | "keywords": [ 22 | "lif-token", 23 | "winding-tree", 24 | "token", 25 | "crowdsale" 26 | ], 27 | "author": "Augusto Lemble ", 28 | "license": "GPLv3", 29 | "dependencies": { 30 | "abi-decoder": "^1.0.8", 31 | "ethereumjs-abi": "^0.6.4", 32 | "jsverify": "^0.8.2", 33 | "lodash": "^4.17.4", 34 | "rimraf": "^2.6.2", 35 | "truffle-hdwallet-provider": "0.0.3", 36 | "zeppelin-solidity": "^1.6.0" 37 | }, 38 | "devDependencies": { 39 | "chai-bignumber": "^2.0.1", 40 | "coveralls": "^2.13.1", 41 | "eslint": "^4.16.0", 42 | "eslint-config-standard": "^10.2.1", 43 | "eslint-plugin-import": "^2.8.0", 44 | "eslint-plugin-node": "^5.2.1", 45 | "eslint-plugin-promise": "^3.6.0", 46 | "eslint-plugin-standard": "^3.0.1", 47 | "ethereumjs-testrpc": "^6.0.1", 48 | "ethereumjs-util": "^5.1.3", 49 | "mocha-lcov-reporter": "^1.3.0", 50 | "solidity-coverage": "^0.2.2", 51 | "truffle": "^4.0.5", 52 | "truffle-hdwallet-provider": "0.0.3" 53 | }, 54 | "engines": { 55 | "node": ">=8.9.4" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ "$SOLIDITY_COVERAGE" = true ]; then 6 | yarn run coveralls 7 | else 8 | yarn lint 9 | WT_DEBUG=true yarn test test/LifToken.js test/Crowdsale.js test/MarketValidationMechanism.js test/VestedPayment.js 10 | WT_DEBUG=true GEN_TESTS_TIMEOUT=400 GEN_TESTS_QTY=40 yarn test test/CrowdsaleGenTest.js 11 | fi 12 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Executes cleanup function at script exit. 4 | trap cleanup EXIT 5 | 6 | cleanup() { 7 | # Kill the testrpc instance that we started (if we started one). 8 | if [ -n "$testrpc_pid" ]; then 9 | kill -9 $testrpc_pid 10 | fi 11 | } 12 | 13 | testrpc_running() { 14 | nc -z localhost 8555 15 | } 16 | 17 | if testrpc_running; then 18 | echo "Using existing testrpc-sc instance" 19 | else 20 | echo "Starting testrpc-sc to generate coverage" 21 | # We define 10 accounts with balance 1M ether, needed for high-value tests. 22 | ./node_modules/ethereumjs-testrpc-sc/build/cli.node.js --gasLimit 0xfffffffffff --port 8555 \ 23 | --account="0xe8280389ca1303a2712a874707fdd5d8ae0437fab9918f845d26fd9919af5a92,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 24 | --account="0xed095a912033d26dc444d2675b33414f0561af170d58c33f394db8812c87a764,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 25 | --account="0xf5556ca108835f04cd7d29b4ac66f139dc12b61396b147674631ce25e6e80b9b,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 26 | --account="0xd1bea55dd05b35be047e409617bc6010b0363f22893b871ceef2adf8e97b9eb9,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 27 | --account="0xfc452929dc8ffd956ebab936ed0f56d71a8c537b0393ea9da4807836942045c5,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 28 | --account="0x12b8b2fe49596ab7f439d324797f4b5457b5bd34e9860b08828e4b01af228d93,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 29 | --account="0x2ed88e3846387d0ae4cca96637df48c201c86079be64d0a17bf492058db6c6eb,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 30 | --account="0x8c6690649d0b31790fceddd6a59decf2b03686bed940a9b85e8105c5e82f7a86,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 31 | --account="0xf809d1a2969bec37e7c14628717092befa82156fb2ebf935ac5420bc522f0d29,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 32 | --account="0x38062255973f02f1b320d8c7762dd286946b3e366f73076995dc859a6346c2ec,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 33 | --account="0x35b5042e809eab0db3252bad02b67436f64453072128ee91c1d4605de70b27c1,10000000000000000000000000000000000000000000000000000000000000000000000000000000" \ 34 | > /dev/null & 35 | testrpc_pid=$! 36 | fi 37 | 38 | SOLIDITY_COVERAGE=true GEN_TESTS_QTY=0 GAS_PRICE=1 ./node_modules/.bin/solidity-coverage 39 | -------------------------------------------------------------------------------- /scripts/coveralls.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | npm run coverage && cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 4 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit script as soon as a command fails. 4 | set -o errexit 5 | 6 | # Executes cleanup function at script exit. 7 | trap cleanup EXIT 8 | 9 | cleanup() { 10 | # Kill the testrpc instance that we started (if we started one and if it's still running). 11 | if [ -n "$testrpc_pid" ] && ps -p $testrpc_pid > /dev/null; then 12 | kill -9 $testrpc_pid 13 | fi 14 | } 15 | 16 | testrpc_port=8545 17 | 18 | testrpc_running() { 19 | nc -z localhost "$testrpc_port" 20 | } 21 | 22 | start_testrpc() { 23 | # We define 10 accounts with balance 1M ether, needed for high-value tests. 24 | local accounts=( 25 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000" 26 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000" 27 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000" 28 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000" 29 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000" 30 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000" 31 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000" 32 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000" 33 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000" 34 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" 35 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501210,1000000000000000000000000" 36 | ) 37 | 38 | node_modules/.bin/testrpc --gasLimit 8000000 --gasPrice 41000000000 "${accounts[@]}" > /dev/null & 39 | 40 | testrpc_pid=$! 41 | } 42 | 43 | if testrpc_running; then 44 | echo "Using existing testrpc instance" 45 | else 46 | echo "Starting our own testrpc instance" 47 | start_testrpc 48 | fi 49 | 50 | node_modules/.bin/truffle test "$@" 51 | -------------------------------------------------------------------------------- /test/Crowdsale.js: -------------------------------------------------------------------------------- 1 | var LifCrowdsale = artifacts.require('./LifCrowdsale.sol'), 2 | LifToken = artifacts.require('./LifToken.sol'), 3 | LifMarketValidationMechanism = artifacts.require('./LifMarketValidationMechanism.sol'); 4 | 5 | let help = require('./helpers'); 6 | 7 | var BigNumber = web3.BigNumber; 8 | 9 | require('chai') 10 | .use(require('chai-bignumber')(BigNumber)) 11 | .should(); 12 | 13 | var latestTime = require('./helpers/latestTime'); 14 | var { duration, increaseTimeTestRPCTo } = require('./helpers/increaseTime'); 15 | 16 | const defaultTimeDelta = duration.days(1); // time delta used in time calculations (for start, end1 & end2) 17 | 18 | const defaults = { 19 | rate1: 100, 20 | rate2: 110, 21 | setWeiLockSeconds: duration.minutes(30), 22 | foundationWalletIndex: 0, 23 | foundersWalletIndex: 1, 24 | }; 25 | 26 | contract('LifToken Crowdsale', function (accounts) { 27 | async function createCrowdsale (params) { 28 | const startTimestamp = params.start === undefined ? (latestTime() + defaultTimeDelta) : params.start, 29 | end1Timestamp = params.end1 === undefined ? (startTimestamp + defaultTimeDelta) : params.end1, 30 | end2Timestamp = params.end2 === undefined ? (end1Timestamp + defaultTimeDelta) : params.end2, 31 | rate1 = params.rate1 === undefined ? defaults.rate1 : params.rate1, 32 | rate2 = params.rate2 === undefined ? defaults.rate2 : params.rate2, 33 | setWeiLockSeconds = params.setWeiLockSeconds === undefined ? defaults.setWeiLockSeconds : params.setWeiLockSeconds, 34 | foundationWallet = params.foundationWallet === undefined 35 | ? accounts[defaults.foundationWalletIndex] : params.foundationWallet, 36 | foundersWallet = params.foundersWallet === undefined 37 | ? accounts[defaults.foundersWalletIndex] : params.foundersWallet; 38 | 39 | const newCrowdsale = await LifCrowdsale.new( 40 | startTimestamp, end1Timestamp, end2Timestamp, rate1, 41 | rate2, setWeiLockSeconds, foundationWallet, foundersWallet 42 | ); 43 | return newCrowdsale; 44 | } 45 | 46 | it('can create a Crowdsale', async function () { 47 | const start = latestTime() + defaultTimeDelta, 48 | end1 = start + defaultTimeDelta, 49 | end2 = end1 + defaultTimeDelta; 50 | 51 | const crowdsale = await createCrowdsale({ 52 | start: start, 53 | end1: end1, 54 | end2: end2, 55 | }); 56 | 57 | assert.equal(start, parseInt(await crowdsale.startTimestamp.call())); 58 | assert.equal(end1, parseInt(await crowdsale.end1Timestamp.call())); 59 | assert.equal(end2, parseInt(await crowdsale.end2Timestamp.call())); 60 | assert.equal(defaults.rate1, parseInt(await crowdsale.rate1.call())); 61 | assert.equal(defaults.rate2, parseInt(await crowdsale.rate2.call())); 62 | assert.equal(accounts[defaults.foundationWalletIndex], parseInt(await crowdsale.foundationWallet.call())); 63 | assert.equal(accounts[defaults.foundersWalletIndex], parseInt(await crowdsale.foundersWallet.call())); 64 | }); 65 | 66 | it('fails to create a Crowdsale with 0x0 as foundation wallet', async function () { 67 | try { 68 | await createCrowdsale({ foundationWallet: help.zeroAddress }); 69 | assert(false, 'create crowdsale should have thrown'); 70 | } catch (e) { 71 | if (!help.isInvalidOpcodeEx(e)) throw e; 72 | } 73 | }); 74 | 75 | it('fails to create a Crowdsale with 0x0 as founders wallet', async function () { 76 | try { 77 | await createCrowdsale({ foundersWallet: help.zeroAddress }); 78 | assert(false, 'create crowdsale should have thrown'); 79 | } catch (e) { 80 | if (!help.isInvalidOpcodeEx(e)) throw e; 81 | } 82 | }); 83 | 84 | it('fails to create a Crowdsale with start timestamp in the past', async function () { 85 | try { 86 | await createCrowdsale({ start: latestTime() - 1 }); 87 | assert(false, 'create crowdsale should have thrown'); 88 | } catch (e) { 89 | if (!help.isInvalidOpcodeEx(e)) throw e; 90 | } 91 | }); 92 | 93 | it('fails to create a Crowdsale with end timestamp not after start timestamp', async function () { 94 | try { 95 | const start = latestTime() + defaultTimeDelta; 96 | await createCrowdsale({ start: start, end1: start }); 97 | assert(false, 'create crowdsale should have thrown'); 98 | } catch (e) { 99 | if (!help.isInvalidOpcodeEx(e)) throw e; 100 | } 101 | }); 102 | 103 | it('fails to create a Crowdsale with end2 timestamp not after end1 timestamp', async function () { 104 | const start = latestTime() + defaultTimeDelta, 105 | end1 = start + defaultTimeDelta; 106 | 107 | try { 108 | await createCrowdsale({ start: start, end1: end1, end2: end1 }); 109 | assert(false, 'create crowdsale should have thrown'); 110 | } catch (e) { 111 | if (!help.isInvalidOpcodeEx(e)) throw e; 112 | } 113 | }); 114 | 115 | it('fails to create a Crowdsale with rate1 === 0', async function () { 116 | try { 117 | await createCrowdsale({ rate1: 0 }); 118 | assert(false, 'create crowdsale should have thrown'); 119 | } catch (e) { 120 | if (!help.isInvalidOpcodeEx(e)) throw e; 121 | } 122 | }); 123 | 124 | it('fails to create a Crowdsale with rate2 === 0', async function () { 125 | try { 126 | await createCrowdsale({ rate2: 0 }); 127 | assert(false, 'create crowdsale should have thrown'); 128 | } catch (e) { 129 | if (!help.isInvalidOpcodeEx(e)) throw e; 130 | } 131 | }); 132 | 133 | it('fails to create a Crowdsale with setWeiLockSeconds === 0', async function () { 134 | try { 135 | await createCrowdsale({ setWeiLockSeconds: 0 }); 136 | assert(false, 'create crowdsale should have thrown'); 137 | } catch (e) { 138 | if (!help.isInvalidOpcodeEx(e)) throw e; 139 | } 140 | }); 141 | 142 | it('returns the current rate at different points in time', async function () { 143 | const start = latestTime() + defaultTimeDelta, 144 | end1 = start + defaultTimeDelta, 145 | end2 = end1 + defaultTimeDelta, 146 | crowdsale = await createCrowdsale({ 147 | start: start, 148 | end1: end1, 149 | end2: end2, 150 | }); 151 | 152 | assert.equal(0, parseInt(await crowdsale.getRate())); 153 | 154 | await increaseTimeTestRPCTo(start); 155 | 156 | assert.equal(defaults.rate1, parseInt(await crowdsale.getRate())); 157 | 158 | await increaseTimeTestRPCTo(end1 - 2); 159 | assert.equal(defaults.rate1, parseInt(await crowdsale.getRate()), 160 | 'rate should still be rate1 close but before end1 timestamp'); 161 | 162 | await increaseTimeTestRPCTo(end1 + 1); 163 | assert.equal(defaults.rate2, parseInt(await crowdsale.getRate()), 164 | 'rate should be rate 2 between end1 and end2'); 165 | 166 | await increaseTimeTestRPCTo(end2 - 2); 167 | assert.equal(defaults.rate2, parseInt(await crowdsale.getRate()), 168 | 'rate should be rate 2 close but before end2 timestamp'); 169 | 170 | await increaseTimeTestRPCTo(end2 + 1); 171 | assert.equal(0, parseInt(await crowdsale.getRate()), 172 | 'rate should be 0 after end2 timestamp'); 173 | }); 174 | 175 | /// buyTokens 176 | 177 | it('handles a buyTokens tx fine', async function () { 178 | const crowdsale = await createCrowdsale({}); 179 | await crowdsale.setWeiPerUSDinTGE(10000); 180 | await increaseTimeTestRPCTo(latestTime() + defaultTimeDelta + 2); 181 | await crowdsale.buyTokens(accounts[6], { value: 1000, from: accounts[5] }); 182 | 183 | assert.equal(1000, await crowdsale.purchases(accounts[6])); 184 | }); 185 | 186 | it('fails on buyTokens from address(0)', async function () { 187 | const crowdsale = await createCrowdsale({}); 188 | await crowdsale.setWeiPerUSDinTGE(10000); 189 | await increaseTimeTestRPCTo(latestTime() + defaultTimeDelta + 2); 190 | try { 191 | await crowdsale.buyTokens(help.zeroAddress, { value: 1000, from: accounts[5] }); 192 | assert(false, 'should have thrown'); 193 | } catch (e) { 194 | assert(help.isInvalidOpcodeEx(e)); 195 | } 196 | }); 197 | 198 | it('fails on buyTokens with rate==0 (before startTimestamp)', async function () { 199 | const crowdsale = await createCrowdsale({}); 200 | await crowdsale.setWeiPerUSDinTGE(10000); 201 | try { 202 | await crowdsale.buyTokens(accounts[5], { value: 1000, from: accounts[5] }); 203 | assert(false, 'should have thrown'); 204 | } catch (e) { 205 | assert(help.isInvalidOpcodeEx(e)); 206 | } 207 | }); 208 | 209 | /// addPrivatePresaleTokens 210 | it('handles an addPrivatePresaleTokens tx fine', async function () { 211 | const crowdsale = await createCrowdsale({}), 212 | rate = defaults.rate1 + 10, 213 | token = LifToken.at(await crowdsale.token()); 214 | 215 | await crowdsale.setWeiPerUSDinTGE(10000); 216 | await crowdsale.addPrivatePresaleTokens(accounts[3], 1000, rate, 217 | { from: accounts[0] }); 218 | 219 | assert.equal(1000 * rate, parseInt(await token.balanceOf(accounts[3])), 220 | 'should mint the tokens to beneficiary on addPrivatePresaleTokens'); 221 | }); 222 | 223 | it('fails on a addPrivatePresaleTokens tx with address(0) as benef.', async function () { 224 | const crowdsale = await createCrowdsale({}), 225 | rate = defaults.rate1 + 10; 226 | 227 | await crowdsale.setWeiPerUSDinTGE(10000); 228 | try { 229 | await crowdsale.addPrivatePresaleTokens(help.zeroAddress, 1000, rate, 230 | { from: accounts[0] }); 231 | assert(false); 232 | } catch (e) { 233 | assert(help.isInvalidOpcodeEx(e)); 234 | } 235 | }); 236 | 237 | it('fails on a addPrivatePresaleTokens tx with low rate', async function () { 238 | const crowdsale = await createCrowdsale({}), 239 | rate = defaults.rate1 - 10; 240 | 241 | await crowdsale.setWeiPerUSDinTGE(10000); 242 | try { 243 | await crowdsale.addPrivatePresaleTokens(accounts[3], 1000, rate, 244 | { from: accounts[0] }); 245 | assert(false); 246 | } catch (e) { 247 | assert(help.isInvalidOpcodeEx(e)); 248 | } 249 | }); 250 | 251 | it('fails on a addPrivatePresaleTokens tx with weiSent === 0', async function () { 252 | const crowdsale = await createCrowdsale({}), 253 | rate = defaults.rate1 + 10; 254 | 255 | await crowdsale.setWeiPerUSDinTGE(10000); 256 | try { 257 | await crowdsale.addPrivatePresaleTokens(accounts[3], 0, rate, 258 | { from: accounts[0] }); 259 | assert(false); 260 | } catch (e) { 261 | assert(help.isInvalidOpcodeEx(e)); 262 | } 263 | }); 264 | 265 | /// claimEth 266 | it('claimEth succeeds after an underfunded finalized crowdsale', async function () { 267 | const start = latestTime() + defaultTimeDelta, 268 | end1 = start + defaultTimeDelta, 269 | end2 = end1 + defaultTimeDelta, 270 | beneficiary = accounts[6], 271 | crowdsale = await createCrowdsale({ 272 | start: start, 273 | end1: end1, 274 | end2: end2, 275 | }), 276 | weiPerUsd = 10000, 277 | weiAmount = 5000000 * weiPerUsd - 1; // exactly USD 1 less than the funding limit 278 | 279 | await crowdsale.setWeiPerUSDinTGE(weiPerUsd); 280 | await increaseTimeTestRPCTo(start + 2); 281 | await crowdsale.buyTokens(beneficiary, { value: weiAmount, from: accounts[5] }); 282 | 283 | await increaseTimeTestRPCTo(end2 + 2); 284 | await crowdsale.finalize(true); 285 | 286 | // TO DO: Find out why the balance difference in this assert 287 | // const balanceBeforeClaim = web3.eth.getBalance(beneficiary); 288 | // const tx = await crowdsale.claimEth({from: beneficiary}); 289 | // 290 | // balanceBeforeClaim.plus(weiAmount).minus(help.txGasCost(tx)). 291 | // should.be.bignumber.equal(web3.eth.getBalance(beneficiary)); 292 | }); 293 | 294 | it('claimEth fails after a funded finalized crowdsale', async function () { 295 | const start = latestTime() + defaultTimeDelta, 296 | end1 = start + defaultTimeDelta, 297 | end2 = end1 + defaultTimeDelta, 298 | crowdsale = await createCrowdsale({ 299 | start: start, 300 | end1: end1, 301 | end2: end2, 302 | }), 303 | weiPerUsd = 10000, 304 | weiAmount = 5000000 * weiPerUsd; 305 | await crowdsale.setWeiPerUSDinTGE(weiPerUsd); 306 | await increaseTimeTestRPCTo(start + 2); 307 | await crowdsale.buyTokens(accounts[6], { value: weiAmount, from: accounts[5] }); 308 | 309 | await increaseTimeTestRPCTo(end2 + 2); 310 | await crowdsale.finalize(true); 311 | 312 | try { 313 | await crowdsale.claimEth({ from: accounts[6] }); 314 | assert(false, 'should have thrown'); 315 | } catch (e) { 316 | assert(help.isInvalidOpcodeEx(e)); 317 | } 318 | }); 319 | 320 | it('claimEth fails after an underfunded non-finalized (but ended) crowdsale ', async function () { 321 | const start = latestTime() + defaultTimeDelta, 322 | end1 = start + defaultTimeDelta, 323 | end2 = end1 + defaultTimeDelta, 324 | beneficiary = accounts[6], 325 | crowdsale = await createCrowdsale({ 326 | start: start, 327 | end1: end1, 328 | end2: end2, 329 | }), 330 | weiPerUsd = 10000, 331 | weiAmount = 5000000 * weiPerUsd - 1; 332 | await crowdsale.setWeiPerUSDinTGE(weiPerUsd); 333 | await increaseTimeTestRPCTo(start + 2); 334 | await crowdsale.buyTokens(beneficiary, { value: weiAmount, from: accounts[5] }); 335 | 336 | await increaseTimeTestRPCTo(end2 + 2); 337 | 338 | try { 339 | await crowdsale.claimEth({ from: beneficiary }); 340 | assert(false, 'should have thrown'); 341 | } catch (e) { 342 | assert(help.isInvalidOpcodeEx(e)); 343 | } 344 | }); 345 | 346 | it('claimEth fails after an underfunded finalized crowdsale for an address with no purchases', async function () { 347 | const start = latestTime() + defaultTimeDelta, 348 | end1 = start + defaultTimeDelta, 349 | end2 = end1 + defaultTimeDelta, 350 | beneficiary = accounts[6], 351 | crowdsale = await createCrowdsale({ 352 | start: start, 353 | end1: end1, 354 | end2: end2, 355 | }), 356 | weiPerUsd = 10000, 357 | weiAmount = 5000000 * weiPerUsd - 1; // exactly USD 1 less than the funding limit 358 | 359 | await crowdsale.setWeiPerUSDinTGE(weiPerUsd); 360 | await increaseTimeTestRPCTo(start + 2); 361 | await crowdsale.buyTokens(beneficiary, { value: weiAmount, from: accounts[5] }); 362 | 363 | await increaseTimeTestRPCTo(end2 + 2); 364 | await crowdsale.finalize(true); 365 | 366 | try { 367 | await crowdsale.claimEth({ from: accounts[8] }); 368 | assert(false, 'should have thrown'); 369 | } catch (e) { 370 | assert(help.isInvalidOpcodeEx(e)); 371 | } 372 | }); 373 | 374 | /// finalize 375 | it('finalize fails when called for the second time', async function () { 376 | const start = latestTime() + defaultTimeDelta, 377 | end1 = start + defaultTimeDelta, 378 | end2 = end1 + defaultTimeDelta, 379 | beneficiary = accounts[6], 380 | crowdsale = await createCrowdsale({ 381 | start: start, 382 | end1: end1, 383 | end2: end2, 384 | }), 385 | weiPerUsd = 10000, 386 | weiAmount = 5000000 * weiPerUsd - 1; // exactly USD 1 less than the funding limit 387 | 388 | await crowdsale.setWeiPerUSDinTGE(weiPerUsd); 389 | await increaseTimeTestRPCTo(start + 2); 390 | await crowdsale.buyTokens(beneficiary, { value: weiAmount, from: accounts[5] }); 391 | 392 | await increaseTimeTestRPCTo(end2 + 2); 393 | await crowdsale.finalize(true); 394 | 395 | try { 396 | await crowdsale.finalize(true); 397 | assert(false, 'should have thrown'); 398 | } catch (e) { 399 | assert(help.isInvalidOpcodeEx(e)); 400 | } 401 | }); 402 | 403 | /// finalize 404 | it('finalize creates the MVM', async function () { 405 | const start = latestTime() + defaultTimeDelta, 406 | end1 = start + defaultTimeDelta, 407 | end2 = end1 + defaultTimeDelta, 408 | beneficiary = accounts[6], 409 | crowdsale = await createCrowdsale({ 410 | start: start, 411 | end1: end1, 412 | end2: end2, 413 | }), 414 | weiPerUsd = 10000, 415 | weiAmount = 10000001 * weiPerUsd; // exactly USD 1 more than the minimum for the MVM 416 | 417 | await crowdsale.setWeiPerUSDinTGE(weiPerUsd); 418 | await increaseTimeTestRPCTo(start + 2); 419 | await crowdsale.buyTokens(beneficiary, { value: weiAmount, from: accounts[5] }); 420 | 421 | await increaseTimeTestRPCTo(end2 + 2); 422 | await crowdsale.finalize(true); 423 | 424 | const MVMAddress = await crowdsale.MVM.call(); 425 | assert(MVMAddress !== help.zeroAddress); 426 | 427 | const MVM = LifMarketValidationMechanism.at(MVMAddress), 428 | MVMInitialWei = await MVM.initialWei(); 429 | MVMInitialWei.should.be.bignumber.equal(1 * weiPerUsd); 430 | }); 431 | 432 | it('finalize not over soft cap does not create the MVM', async function () { 433 | const start = latestTime() + defaultTimeDelta, 434 | end1 = start + defaultTimeDelta, 435 | end2 = end1 + defaultTimeDelta, 436 | beneficiary = accounts[6], 437 | crowdsale = await createCrowdsale({ 438 | start: start, 439 | end1: end1, 440 | end2: end2, 441 | }), 442 | weiPerUsd = 10000, 443 | weiAmount = 10000000 * weiPerUsd; // exactly the soft cap 444 | 445 | await crowdsale.setWeiPerUSDinTGE(weiPerUsd); 446 | await increaseTimeTestRPCTo(start + 2); 447 | await crowdsale.buyTokens(beneficiary, { value: weiAmount, from: accounts[5] }); 448 | 449 | await increaseTimeTestRPCTo(end2 + 2); 450 | await crowdsale.finalize(true); 451 | 452 | const MVMAddress = await crowdsale.MVM.call(); 453 | assert(MVMAddress === help.zeroAddress, 'no MVM should have been created: ' + MVMAddress); 454 | }); 455 | }); 456 | -------------------------------------------------------------------------------- /test/LifChannels.js: -------------------------------------------------------------------------------- 1 | var help = require('./helpers'); 2 | var ethUtils = require('ethereumjs-util'); 3 | 4 | var BigNumber = web3.BigNumber; 5 | 6 | require('chai') 7 | .use(require('chai-bignumber')(BigNumber)) 8 | .should(); 9 | 10 | var LifTokenTest = artifacts.require('./LifTokenTest.sol'); 11 | var LifChannels = artifacts.require('./LifChannels.sol'); 12 | var ECRecovery = artifacts.require('./ECRecovery.sol'); 13 | 14 | var { increaseTimeTestRPC } = require('./helpers/increaseTime'); 15 | 16 | const LOG_EVENTS = true; 17 | 18 | contract('LifToken', function (accounts) { 19 | var token; 20 | var lifChannels; 21 | var eventsWatcher; 22 | 23 | // This private keys should match the ones used by testrpc 24 | const privateKeys = {}; 25 | privateKeys[accounts[0]] = '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200'; 26 | privateKeys[accounts[1]] = '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201'; 27 | privateKeys[accounts[2]] = '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202'; 28 | privateKeys[accounts[3]] = '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203'; 29 | 30 | // Sign a message with a private key, it returns the signature in rpc format 31 | function signMsg (msg, pvKey) { 32 | const sig = ethUtils.ecsign(ethUtils.toBuffer(msg), ethUtils.toBuffer(pvKey)); 33 | return ethUtils.toRpcSig(sig.v, sig.r, sig.s); 34 | } 35 | 36 | beforeEach(async function () { 37 | const ecrecovery = await ECRecovery.new(); 38 | LifChannels.link('ECRecovery', ecrecovery.address); 39 | token = await LifTokenTest.new(); 40 | await token.faucetLif({ from: accounts[1] }); 41 | await token.faucetLif({ from: accounts[2] }); 42 | await token.faucetLif({ from: accounts[3] }); 43 | lifChannels = await LifChannels.new(token.address, 60); 44 | eventsWatcher = lifChannels.allEvents(); 45 | eventsWatcher.watch(function (error, log) { 46 | if (LOG_EVENTS) { 47 | if (error) { 48 | console.log('Error in event:', error); 49 | } else { 50 | console.log('Event:', log.event, ':', log.args); 51 | } 52 | } 53 | }); 54 | }); 55 | 56 | afterEach(function (done) { 57 | eventsWatcher.stopWatching(); 58 | done(); 59 | }); 60 | 61 | it('create channel', async function () { 62 | await token.approve(lifChannels.address, help.lif2LifWei(30), { from: accounts[2] }); 63 | const nonce = 66; 64 | help.debug('Creating channel between ' + accounts[1] + ' and' + accounts[2]); 65 | await lifChannels.openChannel(accounts[1], help.lif2LifWei(30), nonce, { from: accounts[2] }); 66 | const channelInfo = await lifChannels.getChannelInfo(accounts[2], accounts[1], nonce); 67 | assert.equal(channelInfo[1], help.lif2LifWei(30)); 68 | assert.equal(channelInfo[2], 0); 69 | assert.equal(channelInfo[3], 0); 70 | }); 71 | 72 | it('create channel and close it with a mutual agreement from sender', async function () { 73 | await token.approve(lifChannels.address, help.lif2LifWei(30), { from: accounts[2] }); 74 | const nonce = 33; 75 | await lifChannels.openChannel(accounts[1], help.lif2LifWei(30), nonce, { from: accounts[2] }); 76 | help.debug('Sharing sig to send 20 Lif to ' + accounts[1] + ' from ' + accounts[2]); 77 | const hash = await lifChannels.generateBalanceHash(accounts[1], nonce, help.lif2LifWei(20)); 78 | const senderSig = signMsg(hash, privateKeys[accounts[2]]); 79 | assert.equal(accounts[2], 80 | await lifChannels.getSignerOfBalanceHash(accounts[1], nonce, help.lif2LifWei(20), senderSig) 81 | ); 82 | help.debug('Sharing sig to accept 20 Lif from ' + accounts[2] + ' to ' + accounts[1]); 83 | const senderHash = await lifChannels.generateKeccak256(senderSig); 84 | const closingSig = signMsg(senderHash, privateKeys[accounts[1]]); 85 | help.debug('Closing channel from ' + accounts[2]); 86 | await lifChannels.cooperativeClose(accounts[1], nonce, help.lif2LifWei(20), senderSig, closingSig, { from: accounts[1] }); 87 | (await token.balanceOf(accounts[2])).should.be.bignumber 88 | .equal(help.lif2LifWei(30)); 89 | (await token.balanceOf(accounts[1])).should.be.bignumber 90 | .equal(help.lif2LifWei(70)); 91 | }); 92 | 93 | it('create channel and close it with a mutual agreement from receiver', async function () { 94 | await token.approve(lifChannels.address, help.lif2LifWei(30), { from: accounts[2] }); 95 | const nonce = 33; 96 | await lifChannels.openChannel(accounts[1], help.lif2LifWei(30), nonce, { from: accounts[2] }); 97 | help.debug('Sharing sig to send 20 Lif to ' + accounts[1] + ' from ' + accounts[2]); 98 | const hash = await lifChannels.generateBalanceHash(accounts[1], nonce, help.lif2LifWei(20)); 99 | const senderSig = signMsg(hash, privateKeys[accounts[2]]); 100 | assert.equal(accounts[2], 101 | await lifChannels.getSignerOfBalanceHash(accounts[1], nonce, help.lif2LifWei(20), senderSig) 102 | ); 103 | help.debug('Sharing sig to accept 20 Lif from ' + accounts[2] + ' to ' + accounts[1]); 104 | const senderHash = await lifChannels.generateKeccak256(senderSig); 105 | const closingSig = signMsg(senderHash, privateKeys[accounts[1]]); 106 | help.debug('Closing channel from ' + accounts[2]); 107 | await lifChannels.cooperativeClose(accounts[1], nonce, help.lif2LifWei(20), senderSig, closingSig, { from: accounts[2] }); 108 | (await token.balanceOf(accounts[2])).should.be.bignumber 109 | .equal(help.lif2LifWei(30)); 110 | (await token.balanceOf(accounts[1])).should.be.bignumber 111 | .equal(help.lif2LifWei(70)); 112 | }); 113 | 114 | it('create channel and close it from sender with uncooperativeClose', async function () { 115 | await token.approve(lifChannels.address, help.lif2LifWei(30), { from: accounts[2] }); 116 | const nonce = 33; 117 | await lifChannels.openChannel(accounts[1], help.lif2LifWei(30), nonce, { from: accounts[2] }); 118 | help.debug('Sharing sig to send 10 Lif to ' + accounts[1] + ' from ' + accounts[2]); 119 | const hash = await lifChannels.generateBalanceHash(accounts[1], nonce, help.lif2LifWei(10)); 120 | const senderSig = signMsg(hash, privateKeys[accounts[2]]); 121 | assert.equal(accounts[2], 122 | await lifChannels.getSignerOfBalanceHash(accounts[1], nonce, help.lif2LifWei(10), senderSig) 123 | ); 124 | help.debug('Closing channel from ' + accounts[2]); 125 | await lifChannels.uncooperativeClose(accounts[1], nonce, help.lif2LifWei(10), { from: accounts[2] }); 126 | try { 127 | await lifChannels.closeChannel(accounts[1], nonce, { from: accounts[2] }); 128 | assert(false, 'close should have thrown'); 129 | } catch (error) { 130 | if (!help.isInvalidOpcodeEx(error)) throw error; 131 | } 132 | await increaseTimeTestRPC(61); 133 | await lifChannels.closeChannel(accounts[1], nonce, { from: accounts[2] }); 134 | (await token.balanceOf(accounts[2])).should.be.bignumber 135 | .equal(help.lif2LifWei(40)); 136 | (await token.balanceOf(accounts[1])).should.be.bignumber 137 | .equal(help.lif2LifWei(60)); 138 | }); 139 | 140 | it('create channel and close it from receiver after sender chanllenge with different balance', async function () { 141 | await token.approve(lifChannels.address, help.lif2LifWei(30), { from: accounts[2] }); 142 | const nonce = 33; 143 | await lifChannels.openChannel(accounts[1], help.lif2LifWei(30), nonce, { from: accounts[2] }); 144 | help.debug('Sharing sig to send 20 Lif to ' + accounts[1] + ' from ' + accounts[2]); 145 | const hash = await lifChannels.generateBalanceHash(accounts[1], nonce, help.lif2LifWei(20)); 146 | const senderSig = signMsg(hash, privateKeys[accounts[2]]); 147 | assert.equal(accounts[2], 148 | await lifChannels.getSignerOfBalanceHash(accounts[1], nonce, help.lif2LifWei(20), senderSig) 149 | ); 150 | help.debug('Sharing sig to accept 20 Lif from ' + accounts[2] + ' to ' + accounts[1]); 151 | const senderHash = await lifChannels.generateKeccak256(senderSig); 152 | const closingSig = signMsg(senderHash, privateKeys[accounts[1]]); 153 | help.debug('Start close channel request from ' + accounts[2] + ' with 10 lif'); 154 | await lifChannels.uncooperativeClose(accounts[1], nonce, help.lif2LifWei(10), { from: accounts[2] }); 155 | await increaseTimeTestRPC(10); 156 | help.debug('Claim the 20 agreed before from ' + accounts[1]); 157 | await lifChannels.cooperativeClose(accounts[1], nonce, help.lif2LifWei(20), senderSig, closingSig, { from: accounts[1] }); 158 | (await token.balanceOf(accounts[2])).should.be.bignumber 159 | .equal(help.lif2LifWei(30)); 160 | (await token.balanceOf(accounts[1])).should.be.bignumber 161 | .equal(help.lif2LifWei(70)); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /test/LifToken.js: -------------------------------------------------------------------------------- 1 | var help = require('./helpers'); 2 | var _ = require('lodash'); 3 | var ethjsABI = require('ethjs-abi'); 4 | 5 | var BigNumber = web3.BigNumber; 6 | 7 | require('chai') 8 | .use(require('chai-bignumber')(BigNumber)) 9 | .should(); 10 | 11 | var LifToken = artifacts.require('./LifToken.sol'); 12 | var Message = artifacts.require('./Message.sol'); 13 | 14 | function findMethod (abi, methodName, args) { 15 | for (var i = 0; i < abi.length; i++) { 16 | const methodArgs = _.map(abi[i].inputs, 'type').join(','); 17 | if ((abi[i].name === methodName) && (methodArgs === args)) { 18 | return abi[i]; 19 | } 20 | } 21 | } 22 | 23 | const LOG_EVENTS = true; 24 | 25 | contract('LifToken', function (accounts) { 26 | var token; 27 | var eventsWatcher; 28 | 29 | async function executeERC827Method (params) { 30 | const abiMethod = findMethod( 31 | token.abi, params.method, 32 | (params.method === 'transferFrom') 33 | ? 'address,address,uint256,bytes' : 'address,uint256,bytes' 34 | ); 35 | const methodData = ethjsABI.encodeMethod(abiMethod, params.args); 36 | const tx = await token.sendTransaction( 37 | { from: params.from, data: methodData } 38 | ); 39 | return tx; 40 | } 41 | 42 | beforeEach(async function () { 43 | const rate = 100000000000; 44 | const crowdsale = await help.simulateCrowdsale(rate, [40, 30, 20, 10, 0], accounts, 1); 45 | token = LifToken.at(await crowdsale.token.call()); 46 | eventsWatcher = token.allEvents(); 47 | eventsWatcher.watch(function (error, log) { 48 | if (LOG_EVENTS) { 49 | if (error) { 50 | console.log('Error in event:', error); 51 | } else { 52 | console.log('Event:', log.event, ':', log.args); 53 | } 54 | } 55 | }); 56 | }); 57 | 58 | afterEach(function (done) { 59 | eventsWatcher.stopWatching(); 60 | done(); 61 | }); 62 | 63 | it('has name, symbol and decimals', async function () { 64 | assert.equal('Líf', await token.NAME.call()); 65 | assert.equal('LIF', await token.SYMBOL.call()); 66 | assert.equal(18, await token.DECIMALS.call()); 67 | }); 68 | 69 | it('should return the correct allowance amount after approval', async function () { 70 | await token.approve(accounts[2], help.lif2LifWei(10), { from: accounts[1] }); 71 | let allowance = await token.allowance(accounts[1], accounts[2], { from: accounts[1] }); 72 | assert.equal(help.lifWei2Lif(allowance), 10); 73 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 74 | }); 75 | 76 | it('should return correct balances after transfer', async function () { 77 | await token.transfer(accounts[4], help.lif2LifWei(3.55), { from: accounts[1] }); 78 | await help.checkToken(token, accounts, 125, [36.45, 30, 20, 13.55, 0]); 79 | }); 80 | 81 | it('should throw an error when trying to transfer more than balance', async function () { 82 | try { 83 | await token.transfer(accounts[2], help.lif2LifWei(21)); 84 | assert(false, 'transfer should have thrown'); 85 | } catch (error) { 86 | if (!help.isInvalidOpcodeEx(error)) throw error; 87 | } 88 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 89 | }); 90 | 91 | it('should return correct balances after transfering from another account', async function () { 92 | await token.approve(accounts[3], help.lif2LifWei(5), { from: accounts[1] }); 93 | await token.transferFrom(accounts[1], accounts[2], help.lif2LifWei(5), { from: accounts[3] }); 94 | await help.checkToken(token, accounts, 125, [35, 35, 20, 10, 0]); 95 | }); 96 | 97 | it('should throw an error when trying to transfer more than allowed', async function () { 98 | await token.approve(accounts[3], help.lif2LifWei(10), { from: accounts[1] }); 99 | try { 100 | await token.transferFrom(accounts[1], accounts[3], help.lif2LifWei(11), { from: accounts[3] }); 101 | assert(false, 'transferFrom ERC827 should have thrown'); 102 | } catch (error) { 103 | if (!help.isInvalidOpcodeEx(error)) throw error; 104 | } 105 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 106 | }); 107 | 108 | _.forEach([0, 1], function (tokens) { 109 | it('should return correct balances after transfer ERC827 with ' + tokens + ' tokens and show the event on receiver contract', async function () { 110 | let message = await Message.new(); 111 | help.abiDecoder.addABI(Message._json.abi); 112 | 113 | let data = message.contract.showMessage.getData(web3.toHex(123456), 666, 'Transfer Done'); 114 | 115 | let transaction = await executeERC827Method({ 116 | method: 'transfer', 117 | args: [message.contract.address, help.lif2LifWei(tokens), data], 118 | from: accounts[1], 119 | }); 120 | let decodedEvents = help.abiDecoder.decodeLogs(transaction.receipt.logs); 121 | 122 | assert.deepEqual(['Transfer', 'Show'], _.map(decodedEvents, (e) => e.name), 123 | 'triggered a Show event in Message and Transfer in the token'); 124 | 125 | assert.deepEqual( 126 | [accounts[1], message.contract.address, help.lif2LifWei(tokens)], 127 | _.map(decodedEvents[0].events, (e) => e.value), 128 | 'triggered the correct Transfer event' 129 | ); 130 | 131 | assert.equal(help.lif2LifWei(tokens), await token.balanceOf(message.contract.address)); 132 | 133 | await help.checkToken(token, accounts, 125, [40 - tokens, 30, 20, 10, 0]); 134 | }); 135 | }); 136 | 137 | _.forEach([0, 1], function (tokens) { 138 | it('should return correct balances after transferFrom ERC827 with ' + tokens + ' tokens and show the event on receiver contract', async function () { 139 | let message = await Message.new(); 140 | help.abiDecoder.addABI(Message._json.abi); 141 | 142 | let data = message.contract.showMessage.getData(web3.toHex(123456), 666, 'Transfer Done'); 143 | 144 | const lifWei = help.lif2LifWei(tokens); 145 | 146 | await token.approve(accounts[2], lifWei, { from: accounts[1] }); 147 | 148 | let transaction = await executeERC827Method({ 149 | method: 'transferFrom', 150 | args: [accounts[1], message.contract.address, lifWei, data], 151 | from: accounts[2], 152 | }); 153 | 154 | let decodedEvents = help.abiDecoder.decodeLogs(transaction.receipt.logs); 155 | 156 | assert.deepEqual(['Transfer', 'Show'], _.map(decodedEvents, (e) => e.name)); 157 | assert.equal(lifWei, await token.balanceOf(message.contract.address)); 158 | 159 | await help.checkToken(token, accounts, 125, [40 - tokens, 30, 20, 10, 0]); 160 | }); 161 | }); 162 | 163 | it('should return correct balances after approve and show the event on receiver contract', async function () { 164 | let message = await Message.new(); 165 | help.abiDecoder.addABI(Message._json.abi); 166 | 167 | let data = message.contract.showMessage.getData(web3.toHex(123456), 666, 'Transfer Done'); 168 | 169 | let transaction = await executeERC827Method({ 170 | method: 'approve', 171 | args: [message.contract.address, help.lif2LifWei(1000), data], 172 | from: accounts[1], 173 | }); 174 | let decodedEvents = help.abiDecoder.decodeLogs(transaction.receipt.logs); 175 | 176 | assert.equal(2, decodedEvents.length); 177 | 178 | new BigNumber(help.lif2LifWei(1000)).should.be.bignumber.equal(await token.allowance(accounts[1], message.contract.address)); 179 | 180 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 181 | }); 182 | 183 | it('should fail on approve ERC827 when spender is the same LifToken contract', async function () { 184 | let message = await Message.new(); 185 | let data = message.contract.showMessage.getData(web3.toHex(123456), 666, 'Transfer Done'); 186 | 187 | try { 188 | await executeERC827Method({ 189 | method: 'approve', 190 | args: [token.contract.address, help.lif2LifWei(1000), data], 191 | from: accounts[1], 192 | }); 193 | assert(false, 'approve ERC827 should have thrown because the spender should not be the LifToken itself'); 194 | } catch (e) { 195 | if (!help.isInvalidOpcodeEx(e)) throw e; 196 | } 197 | }); 198 | 199 | it('should fail inside approve ERC827', async function () { 200 | let message = await Message.new(); 201 | help.abiDecoder.addABI(Message._json.abi); 202 | 203 | let data = message.contract.fail.getData(); 204 | 205 | try { 206 | await executeERC827Method({ 207 | method: 'approve', 208 | args: [message.contract.address, help.lif2LifWei(10), data], 209 | from: accounts[1], 210 | }); 211 | assert(false, 'approve ERC827 should have raised'); 212 | } catch (e) { 213 | assert(help.isInvalidOpcodeEx(e)); 214 | } 215 | 216 | // approval should not have gone through so allowance is still 0 217 | new BigNumber(0).should.be.bignumber 218 | .equal(await token.allowance(accounts[1], message.contract.address)); 219 | 220 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 221 | }); 222 | 223 | it('should fail inside transfer ERC827', async function () { 224 | let message = await Message.new(); 225 | help.abiDecoder.addABI(Message._json.abi); 226 | 227 | let data = message.contract.fail.getData(); 228 | 229 | try { 230 | await executeERC827Method({ 231 | method: 'transfer', 232 | args: [message.contract.address, help.lif2LifWei(10), data], 233 | from: accounts[1], 234 | }); 235 | assert(false, 'transfer ERC827 should have failed'); 236 | } catch (e) { 237 | assert(help.isInvalidOpcodeEx(e)); 238 | } 239 | 240 | // transfer ERC827 should not have gone through, so balance is still 0 241 | new BigNumber(0).should.be.bignumber 242 | .equal(await token.balanceOf(message.contract.address)); 243 | 244 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 245 | }); 246 | 247 | it('should fail inside transferFrom ERC827', async function () { 248 | let message = await Message.new(); 249 | help.abiDecoder.addABI(Message._json.abi); 250 | 251 | let data = message.contract.fail.getData(); 252 | 253 | await token.approve(accounts[1], help.lif2LifWei(10), { from: accounts[2] }); 254 | 255 | try { 256 | await executeERC827Method({ 257 | method: 'transferFrom', 258 | args: [accounts[2], message.contract.address, help.lif2LifWei(10), data], 259 | from: accounts[1], 260 | }); 261 | assert(false, 'transferFrom ERC827 should have thrown'); 262 | } catch (e) { 263 | assert(help.isInvalidOpcodeEx(e)); 264 | } 265 | 266 | // transferFrom ERC827 should have failed so balance is still 0 but allowance is 10 267 | new BigNumber(help.lif2LifWei(10)).should.be.bignumber 268 | .equal(await token.allowance(accounts[2], accounts[1])); 269 | new BigNumber(0).should.be.bignumber 270 | .equal(await token.balanceOf(message.contract.address)); 271 | 272 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 273 | }); 274 | 275 | it('should fail transfer ERC827 when using LifToken contract address as receiver', async function () { 276 | let message = await Message.new(); 277 | let data = message.contract.showMessage.getData(web3.toHex(123456), 666, 'Transfer Done'); 278 | 279 | try { 280 | await executeERC827Method({ 281 | method: 'transfer', 282 | args: [message.contract.address, help.lif2LifWei(1000), data], 283 | from: accounts[1], 284 | }); 285 | assert(false, 'transfer ERC827 should have thrown'); 286 | } catch (error) { 287 | if (!help.isInvalidOpcodeEx(error)) throw error; 288 | } 289 | 290 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 291 | }); 292 | 293 | it('should fail transferFrom ERC827 when using LifToken contract address as receiver', async function () { 294 | let message = await Message.new(); 295 | await token.approve(accounts[1], help.lif2LifWei(1), { from: accounts[3] }); 296 | let data = message.contract.showMessage.getData(web3.toHex(123456), 666, 'Transfer Done'); 297 | 298 | try { 299 | await executeERC827Method({ 300 | method: 'transferFrom', 301 | args: [accounts[3], token.contract.address, help.lif2LifWei(1), data], 302 | from: accounts[1], 303 | }); 304 | assert(false, 'transferFrom ERC827 should have thrown'); 305 | } catch (error) { 306 | if (!help.isInvalidOpcodeEx(error)) throw error; 307 | } 308 | 309 | await help.checkToken(token, accounts, 125, [40, 30, 20, 10, 0]); 310 | }); 311 | 312 | it('can burn tokens', async function () { 313 | let totalSupply = await token.totalSupply.call(); 314 | new BigNumber(0).should.be.bignumber.equal(await token.balanceOf(accounts[5])); 315 | 316 | let initialBalance = web3.toWei(1); 317 | await token.transfer(accounts[5], initialBalance, { from: accounts[1] }); 318 | initialBalance.should.be.bignumber.equal(await token.balanceOf(accounts[5])); 319 | 320 | let burned = web3.toWei(0.3); 321 | 322 | assert.equal(accounts[0], await token.owner()); 323 | 324 | // pause the token 325 | await token.pause({ from: accounts[0] }); 326 | 327 | try { 328 | await token.burn(burned, { from: accounts[5] }); 329 | assert(false, 'burn should have thrown'); 330 | } catch (error) { 331 | if (!help.isInvalidOpcodeEx(error)) throw error; 332 | } 333 | await token.unpause({ from: accounts[0] }); 334 | 335 | // now burn should work 336 | await token.burn(burned, { from: accounts[5] }); 337 | 338 | new BigNumber(initialBalance).minus(burned) 339 | .should.be.bignumber.equal(await token.balanceOf(accounts[5])); 340 | totalSupply.minus(burned).should.be.bignumber.equal(await token.totalSupply.call()); 341 | }); 342 | }); 343 | -------------------------------------------------------------------------------- /test/LifTokenTest.js: -------------------------------------------------------------------------------- 1 | 2 | var BigNumber = web3.BigNumber; 3 | 4 | require('chai') 5 | .use(require('chai-bignumber')(BigNumber)) 6 | .should(); 7 | 8 | var LifTokenTest = artifacts.require('./LifTokenTest.sol'); 9 | 10 | const LOG_EVENTS = true; 11 | 12 | contract('LifTokenTest', function (accounts) { 13 | var token; 14 | var eventsWatcher; 15 | 16 | beforeEach(async function () { 17 | token = await LifTokenTest.new(); 18 | eventsWatcher = token.allEvents(); 19 | eventsWatcher.watch(function (error, log) { 20 | if (LOG_EVENTS) { 21 | if (error) { 22 | console.log('Error in event:', error); 23 | } else { 24 | console.log('Event:', log.event, ':', log.args); 25 | } 26 | } 27 | }); 28 | }); 29 | 30 | afterEach(function (done) { 31 | eventsWatcher.stopWatching(); 32 | done(); 33 | }); 34 | 35 | it('has name, symbol and decimals', async function () { 36 | new BigNumber(50000000000000000000) 37 | .should.be.bignumber.equal(await token.MAX_LIF_FAUCET.call()); 38 | }); 39 | 40 | it('should return the correct balance amount after claiming tokens', async function () { 41 | await token.faucetLif(); 42 | new BigNumber(50000000000000000000) 43 | .should.be.bignumber.equal(await token.balanceOf(accounts[0])); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/MarketValidationMechanism.js: -------------------------------------------------------------------------------- 1 | var help = require('./helpers'); 2 | var commands = require('./commands'); 3 | require('lodash'); 4 | 5 | var BigNumber = web3.BigNumber; 6 | 7 | require('chai') 8 | .use(require('chai-bignumber')(BigNumber)) 9 | .should(); 10 | 11 | var LifMarketValidationMechanism = artifacts.require('./LifMarketValidationMechanism.sol'); 12 | var LifToken = artifacts.require('./LifToken.sol'); 13 | 14 | var latestTime = require('./helpers/latestTime'); 15 | var { increaseTimeTestRPC, increaseTimeTestRPCTo, duration } = require('./helpers/increaseTime'); 16 | 17 | contract('Market validation Mechanism', function (accounts) { 18 | it('can be created', async function () { 19 | const token = await LifToken.new({ from: accounts[0] }), 20 | start = latestTime() + 5; 21 | const mvm = await LifMarketValidationMechanism.new(token.address, start, 22 | 100, 24, accounts[1], { from: accounts[0] }); 23 | 24 | assert.equal(token.address, await mvm.lifToken.call()); 25 | assert.equal(start, await mvm.startTimestamp.call()); 26 | assert.equal(100, await mvm.secondsPerPeriod.call()); 27 | assert.equal(24, await mvm.totalPeriods.call()); 28 | assert.equal(accounts[1], await mvm.foundationAddr.call()); 29 | }); 30 | 31 | it('fails to create with 0x0 as token address', async function () { 32 | try { 33 | await LifMarketValidationMechanism.new(help.zeroAddress, latestTime() + 5, 34 | 100, 24, accounts[1], { from: accounts[0] }); 35 | assert(false, 'should have thrown'); 36 | } catch (e) { 37 | assert(help.isInvalidOpcodeEx(e)); 38 | } 39 | }); 40 | 41 | it('fails to create with start timestamp not in future', async function () { 42 | const token = await LifToken.new({ from: accounts[0] }); 43 | 44 | try { 45 | await LifMarketValidationMechanism.new(token.address, latestTime(), 46 | 100, 24, accounts[1], { from: accounts[0] }); 47 | assert(false, 'should have thrown'); 48 | } catch (e) { 49 | assert(help.isInvalidOpcodeEx(e)); 50 | } 51 | }); 52 | 53 | it('fails to create with seconds per period === 0', async function () { 54 | const token = await LifToken.new({ from: accounts[0] }); 55 | 56 | try { 57 | await LifMarketValidationMechanism.new(token.address, latestTime() + 5, 58 | 0, 24, accounts[1], { from: accounts[0] }); 59 | assert(false, 'should have thrown'); 60 | } catch (e) { 61 | assert(help.isInvalidOpcodeEx(e)); 62 | } 63 | }); 64 | 65 | it('fails to create with total periods not 24 or 48', async function () { 66 | const token = await LifToken.new({ from: accounts[0] }); 67 | 68 | await LifMarketValidationMechanism.new(token.address, latestTime() + 5, 69 | 100, 24, accounts[1], { from: accounts[0] }); 70 | await LifMarketValidationMechanism.new(token.address, latestTime() + 5, 71 | 100, 48, accounts[1], { from: accounts[0] }); 72 | 73 | let tryCreateAndFail = async function (periods) { 74 | try { 75 | await LifMarketValidationMechanism.new(token.address, latestTime() + 5, 76 | 100, periods, accounts[1], { from: accounts[0] }); 77 | assert(false, 'should have thrown'); 78 | } catch (e) { 79 | assert(help.isInvalidOpcodeEx(e)); 80 | } 81 | }; 82 | await tryCreateAndFail(27); 83 | await tryCreateAndFail(0); 84 | await tryCreateAndFail(1); 85 | await tryCreateAndFail(23); 86 | await tryCreateAndFail(72); 87 | }); 88 | 89 | it('fails to create with 0x0 as foundation wallet', async function () { 90 | const token = await LifToken.new({ from: accounts[0] }); 91 | 92 | try { 93 | await LifMarketValidationMechanism.new(token.address, latestTime() + 5, 94 | 100, 24, help.addressZero, { from: accounts[0] }); 95 | assert(false, 'should have thrown'); 96 | } catch (e) { 97 | assert(help.isInvalidOpcodeEx(e)); 98 | } 99 | }); 100 | 101 | it('allows calling fund exactly once', async function () { 102 | const token = await LifToken.new({ from: accounts[0] }), 103 | mvm = await LifMarketValidationMechanism.new(token.address, latestTime() + 5, 104 | 100, 24, accounts[1], { from: accounts[0] }); 105 | 106 | // mint some tokens, fund fails otherwise b/c it divides weiSent with tokenSupply 107 | await token.mint(accounts[5], 100, { from: accounts[0] }); 108 | await mvm.fund({ from: accounts[0] }); // it just works, even with value === 0 109 | 110 | try { 111 | await mvm.fund({ from: accounts[0] }); // now it fails 112 | assert(false, 'should have thrown'); 113 | } catch (e) { 114 | assert(help.isInvalidOpcodeEx(e)); 115 | } 116 | }); 117 | 118 | it('allows calling calculateDistributionPeriods exactly once', async function () { 119 | const token = await LifToken.new({ from: accounts[0] }), 120 | mvm = await LifMarketValidationMechanism.new(token.address, latestTime() + 5, 121 | 100, 24, accounts[1], { from: accounts[0] }); 122 | 123 | await mvm.calculateDistributionPeriods(); // it just works 124 | 125 | try { 126 | await mvm.calculateDistributionPeriods(); // now it fails 127 | assert(false, 'should have thrown'); 128 | } catch (e) { 129 | assert(help.isInvalidOpcodeEx(e)); 130 | } 131 | }); 132 | 133 | it('can call getCurrentPeriodIndex only after it has started', async function () { 134 | const token = await LifToken.new({ from: accounts[0] }), 135 | start = latestTime() + 10, 136 | mvm = await LifMarketValidationMechanism.new(token.address, start, 137 | 100, 24, accounts[1], { from: accounts[0] }); 138 | 139 | await mvm.calculateDistributionPeriods(); 140 | 141 | // it first fails because we are before start 142 | try { 143 | await mvm.getCurrentPeriodIndex.call(); 144 | assert(false, 'should have thrown'); 145 | } catch (e) { 146 | assert(help.isInvalidOpcodeEx(e)); 147 | } 148 | 149 | await increaseTimeTestRPCTo(start); 150 | 151 | assert.equal(0, await mvm.getCurrentPeriodIndex.call()); 152 | }); 153 | 154 | it('can call getAccumulatedDistributionPercentage only before it has finished', async function () { 155 | const token = await LifToken.new({ from: accounts[0] }), 156 | start = latestTime() + 10, 157 | mvm = await LifMarketValidationMechanism.new(token.address, start, 158 | 100, 24, accounts[1], { from: accounts[0] }); 159 | 160 | await mvm.calculateDistributionPeriods(); 161 | 162 | try { 163 | await mvm.getAccumulatedDistributionPercentage.call(); 164 | assert(false, 'should have thrown because we are before MVM start timestamp'); 165 | } catch (e) { 166 | assert(help.isInvalidOpcodeEx(e)); 167 | } 168 | 169 | await increaseTimeTestRPCTo(start); 170 | assert.equal(0, parseInt(await mvm.getAccumulatedDistributionPercentage.call())); 171 | 172 | await increaseTimeTestRPCTo(start + 100); 173 | // 18 comes from distributionDeltas in next test, also 99 in next assertion 174 | assert.equal(18, parseInt(await mvm.getAccumulatedDistributionPercentage.call())); 175 | 176 | await increaseTimeTestRPCTo(start + 200); 177 | assert.equal(18 + 99, parseInt(await mvm.getAccumulatedDistributionPercentage.call())); 178 | 179 | await increaseTimeTestRPCTo(start + 100 * 24); 180 | try { 181 | await mvm.getAccumulatedDistributionPercentage.call(); 182 | assert(false, 'should have thrown because we are past the MVM lifetime'); 183 | } catch (e) { 184 | assert(help.isInvalidOpcodeEx(e)); 185 | } 186 | }); 187 | 188 | it('can send tokens, but throws when tokens === 0', async function () { 189 | const token = await LifToken.new({ from: accounts[0] }), 190 | start = latestTime() + 5, 191 | mvm = await LifMarketValidationMechanism.new(token.address, start, 192 | 100, 24, accounts[1], { from: accounts[0] }); 193 | 194 | // mint some tokens, fund fails otherwise b/c it divides weiSent with tokenSupply 195 | await token.mint(accounts[5], 100, { from: accounts[0] }); 196 | await mvm.fund({ value: 100, from: accounts[0] }); // it just works 197 | await mvm.calculateDistributionPeriods(); 198 | 199 | await increaseTimeTestRPCTo(start); 200 | 201 | await token.approve(mvm.address, 100, { from: accounts[5] }); 202 | 203 | try { 204 | await mvm.sendTokens(0, { from: accounts[5] }); 205 | assert(false, 'should have thrown'); 206 | } catch (e) { 207 | assert(help.isInvalidOpcodeEx(e)); 208 | } 209 | 210 | assert.equal(100, parseInt(await token.totalSupply.call())); 211 | await mvm.sendTokens(50, { from: accounts[5] }); 212 | 213 | assert.equal(50, parseInt(await token.totalSupply.call()), 214 | 'total supply has decrease to 50 because of burned tokens'); 215 | }); 216 | 217 | it('validates that only the foundation can claim wei', async function () { 218 | const token = await LifToken.new({ from: accounts[0] }), 219 | start = latestTime() + 5, 220 | foundationWallet = accounts[1], 221 | otherAccount = accounts[4], 222 | mvm = await LifMarketValidationMechanism.new(token.address, start, 223 | 100, 24, foundationWallet, { from: accounts[0] }); 224 | 225 | // mint some tokens, fund fails otherwise b/c it divides weiSent with tokenSupply 226 | await token.mint(accounts[5], 100, { from: accounts[0] }); 227 | await mvm.fund({ value: 100, from: accounts[0] }); 228 | await mvm.calculateDistributionPeriods(); 229 | 230 | await increaseTimeTestRPCTo(start + 2000); 231 | 232 | // works 233 | await mvm.claimWei(1, { from: foundationWallet }); 234 | 235 | try { 236 | // fails 237 | await mvm.claimWei(1, { from: otherAccount }); 238 | assert(false, 'should have thrown'); 239 | } catch (e) { 240 | assert(help.isInvalidOpcodeEx(e)); 241 | } 242 | 243 | // works 244 | await mvm.claimWei(1, { from: foundationWallet }); 245 | }); 246 | 247 | it('Create 24 months MM', async function () { 248 | const mmInitialBalance = 20000000; 249 | const totalTokenSupply = 100; 250 | const rate = totalTokenSupply / web3.fromWei(mmInitialBalance + 10000000, 'ether'); 251 | const crowdsale = await help.simulateCrowdsale(rate, [totalTokenSupply], accounts, 1); 252 | const mm = LifMarketValidationMechanism.at(await crowdsale.MVM.call()); 253 | 254 | assert.equal(mmInitialBalance, parseInt(await web3.eth.getBalance(mm.address))); 255 | assert.equal(mmInitialBalance, parseInt(await mm.initialWei.call())); 256 | assert.equal(24, parseInt(await mm.totalPeriods.call())); 257 | 258 | let distributionDeltas = [ 259 | 0, 18, 99, 234, 416, 640, 260 | 902, 1202, 1536, 1905, 2305, 2738, 261 | 3201, 3693, 4215, 4766, 5345, 5951, 262 | 6583, 7243, 7929, 8640, 9377, 10138, 263 | ]; 264 | 265 | let accumDistribution = 0; 266 | 267 | for (var i = 0; i < distributionDeltas.length; i++) { 268 | accumDistribution += distributionDeltas[i]; 269 | assert.equal(accumDistribution, parseInt((await mm.periods.call(i)))); 270 | } 271 | 272 | // just a few examples to double-check 273 | assert.equal(1407, parseInt(await mm.periods.call(5))); 274 | assert.equal(78938, parseInt(await mm.periods.call(22))); 275 | }); 276 | 277 | it('Create 48 months MM', async function () { 278 | const mmInitialBalance = 50000000; 279 | const totalTokenSupply = 100; 280 | const rate = totalTokenSupply / web3.fromWei(mmInitialBalance + 10000000, 'ether'); 281 | const crowdsale = await help.simulateCrowdsale(rate, [totalTokenSupply], accounts, 1); 282 | const mm = LifMarketValidationMechanism.at(await crowdsale.MVM.call()); 283 | 284 | assert.equal(mmInitialBalance, parseInt(await web3.eth.getBalance(mm.address))); 285 | assert.equal(mmInitialBalance, parseInt(await mm.initialWei.call())); 286 | assert.equal(48, parseInt(await mm.totalPeriods.call())); 287 | 288 | let distributionDeltas = [ 289 | 0, 3, 15, 36, 63, 97, 290 | 137, 183, 233, 289, 350, 416, 291 | 486, 561, 641, 724, 812, 904, 292 | 1000, 1101, 1205, 1313, 1425, 1541, 293 | 1660, 1783, 1910, 2041, 2175, 2312, 294 | 2454, 2598, 2746, 2898, 3053, 3211, 295 | 3373, 3537, 3706, 3877, 4052, 4229, 296 | 4410, 4595, 4782, 4972, 5166, 5363, 297 | ]; 298 | 299 | let accumDistribution = 0; 300 | 301 | for (var i = 0; i < distributionDeltas.length; i++) { 302 | accumDistribution += distributionDeltas[i]; 303 | assert.equal(accumDistribution, parseInt((await mm.periods.call(i)))); 304 | } 305 | 306 | // just a few examples to double-check 307 | assert.equal(214, parseInt(await mm.periods.call(5))); 308 | assert.equal(11994, parseInt(await mm.periods.call(22))); 309 | assert.equal(94438, parseInt(await mm.periods.call(47))); 310 | }); 311 | 312 | it('should return correct periods using getCurrentPeriodIndex', async function () { 313 | const mmInitialBalance = 20000000; 314 | const totalTokenSupply = 100; 315 | const rate = totalTokenSupply / web3.fromWei(mmInitialBalance + 10000000, 'ether'); 316 | const crowdsale = await help.simulateCrowdsale(rate, [totalTokenSupply], accounts, 1); 317 | const mm = LifMarketValidationMechanism.at(await crowdsale.MVM.call()); 318 | 319 | assert.equal(24, parseInt(await mm.totalPeriods.call())); 320 | 321 | const startTimestamp = parseInt(await mm.startTimestamp.call()); 322 | 323 | await increaseTimeTestRPCTo(startTimestamp); 324 | assert.equal(0, parseInt(await mm.getCurrentPeriodIndex.call())); 325 | await increaseTimeTestRPC(duration.days(30)); 326 | assert.equal(1, parseInt(await mm.getCurrentPeriodIndex())); 327 | await increaseTimeTestRPC(duration.days(30)); 328 | assert.equal(2, parseInt(await mm.getCurrentPeriodIndex())); 329 | await increaseTimeTestRPC(duration.days(30)); 330 | assert.equal(3, parseInt(await mm.getCurrentPeriodIndex())); 331 | await increaseTimeTestRPC(duration.days(30)); 332 | assert.equal(4, parseInt(await mm.getCurrentPeriodIndex())); 333 | }); 334 | 335 | it('should return correct periods after pausing/unpausing using getCurrentPeriodIndex', async function () { 336 | const mmInitialBalance = 20000000; 337 | const totalTokenSupply = 100; 338 | const rate = totalTokenSupply / web3.fromWei(mmInitialBalance + 10000000, 'ether'); 339 | const crowdsale = await help.simulateCrowdsale(rate, [totalTokenSupply], accounts, 1); 340 | const mm = LifMarketValidationMechanism.at(await crowdsale.MVM.call()); 341 | 342 | assert.equal(24, parseInt(await mm.totalPeriods.call())); 343 | 344 | const startTimestamp = parseInt(await mm.startTimestamp.call()); 345 | 346 | await increaseTimeTestRPCTo(startTimestamp); 347 | assert.equal(0, parseInt(await mm.getCurrentPeriodIndex())); 348 | await increaseTimeTestRPCTo(startTimestamp + duration.days(30)); 349 | assert.equal(1, parseInt(await mm.getCurrentPeriodIndex())); 350 | await mm.pause({ from: accounts[0] }); 351 | await increaseTimeTestRPC(duration.days(30) * 3); 352 | await mm.unpause({ from: accounts[0] }); 353 | assert.equal(1, parseInt(await mm.getCurrentPeriodIndex())); 354 | await increaseTimeTestRPC(duration.days(30)); 355 | assert.equal(2, parseInt(await mm.getCurrentPeriodIndex())); 356 | await increaseTimeTestRPC(duration.days(30)); 357 | assert.equal(3, parseInt(await mm.getCurrentPeriodIndex())); 358 | await mm.pause({ from: accounts[0] }); 359 | await increaseTimeTestRPC(duration.days(30) * 2); 360 | await mm.unpause({ from: accounts[0] }); 361 | await increaseTimeTestRPC(duration.days(30)); 362 | assert.equal(4, parseInt(await mm.getCurrentPeriodIndex())); 363 | }); 364 | 365 | it('allows only the owner to pause & unpause the MVM', async function () { 366 | const mmInitialBalance = 20000000, 367 | totalTokenSupply = 100, 368 | rate = totalTokenSupply / web3.fromWei(mmInitialBalance + 10000000, 'ether'), 369 | crowdsale = await help.simulateCrowdsale(rate, [totalTokenSupply], accounts, 1), 370 | mvm = LifMarketValidationMechanism.at(await crowdsale.MVM.call()); 371 | 372 | const startTimestamp = parseInt(await mvm.startTimestamp.call()); 373 | 374 | await increaseTimeTestRPCTo(startTimestamp); 375 | 376 | try { 377 | await mvm.unpause({ from: accounts[0] }); 378 | assert(false, 'unpause should throw because it is not paused'); 379 | } catch (e) { 380 | // do nothing 381 | } 382 | 383 | try { 384 | await mvm.pause({ from: accounts[1] }); 385 | assert(false, 'pause should have thrown because it was not made by owner'); 386 | } catch (e) { 387 | // do nothing 388 | } 389 | 390 | await mvm.pause({ from: accounts[0] }); 391 | assert.equal(true, await mvm.paused.call(), 'mvm should be paused'); 392 | 393 | try { 394 | await mvm.pause({ from: accounts[0] }); 395 | assert(false, 'pause should throw because it is paused already'); 396 | } catch (e) { 397 | // do nothing 398 | } 399 | 400 | try { 401 | await mvm.unpause({ from: accounts[1] }); 402 | assert(false, 'unpause should have thrown because it was not made by owner'); 403 | } catch (e) { 404 | // do nothing 405 | } 406 | 407 | await mvm.unpause({ from: accounts[0] }); 408 | assert.equal(false, await mvm.paused.call(), 'mvm should not be paused'); 409 | 410 | // can pause again 411 | await mvm.pause({ from: accounts[0] }); 412 | assert.equal(true, await mvm.paused.call(), 'mvm should be paused'); 413 | }); 414 | 415 | const periods = 24; 416 | const tokenTotalSupply = 3000; 417 | let customerAddressIndex = 1; 418 | 419 | var checkScenarioProperties = async function (data, mm, customer) { 420 | // help.debug('checking scenario', data); 421 | 422 | assert.equal(data.MVMMonth, await mm.getCurrentPeriodIndex()); 423 | data.MVMWeiBalance.should.be.bignumber.equal(web3.eth.getBalance(mm.address)); 424 | data.MVMLifBalance.should.be.bignumber.equal(await data.token.balanceOf(mm.address)); 425 | 426 | new BigNumber(web3.toWei(tokenTotalSupply, 'ether')) 427 | .minus(data.MVMBurnedTokens) 428 | .should.be.bignumber.equal(await data.token.totalSupply.call()); 429 | data.MVMBurnedTokens.should.be.bignumber.equal(await mm.totalBurnedTokens.call()); 430 | 431 | if (data.MVMMonth < data.MVMPeriods) { 432 | data.MVMBuyPrice.should.be.bignumber.equal(await mm.getBuyPrice()); 433 | assert.equal(data.claimablePercentage, parseInt(await mm.getAccumulatedDistributionPercentage())); 434 | } 435 | 436 | assert.equal(data.MVMMonth >= data.MVMPeriods, await mm.isFinished()); 437 | 438 | // TO DO: The problem is probably in the gas calculcation when sending tokens to MVM 439 | // data.ethBalances[customerAddressIndex].should.be.bignumber.equal(web3.eth.getBalance(customer)); 440 | data.balances[customerAddressIndex].should.be.bignumber.equal(await data.token.balanceOf(customer)); 441 | 442 | data.MVMMaxClaimableWei.should.be.bignumber.equal(await mm.getMaxClaimableWeiAmount()); 443 | 444 | data.MVMClaimedWei.should.be.bignumber.equal(await mm.totalWeiClaimed.call()); 445 | }; 446 | 447 | it('should go through scenario with some claims and sells on the Market Maker', async function () { 448 | // Create MM with balance of 200 ETH and 100 tokens in circulation, 449 | const priceFactor = 100000; 450 | 451 | const startingMMBalance = new BigNumber(web3.toWei(200, 'ether')); 452 | const weiPerUSD = parseInt(web3.toWei(200 / 20000000, 'ether')); 453 | const tokensInCrowdsale = new BigNumber(tokenTotalSupply).mul(0.8).floor(); 454 | const rate = tokensInCrowdsale / web3.fromWei(startingMMBalance.plus(web3.toWei(100, 'ether')), 'ether'); 455 | 456 | const foundationWalletIndex = 0, 457 | foundationWallet = accounts[foundationWalletIndex]; 458 | 459 | const crowdsale = await help.simulateCrowdsale(rate, [tokensInCrowdsale], accounts, weiPerUSD); 460 | const token = LifToken.at(await crowdsale.token.call()); 461 | const mm = LifMarketValidationMechanism.at(await crowdsale.MVM.call()); 462 | let customer = accounts[customerAddressIndex]; 463 | const initialBuyPrice = startingMMBalance.mul(priceFactor).dividedBy(help.lif2LifWei(tokenTotalSupply)).floor(); 464 | 465 | help.lif2LifWei(tokenTotalSupply).should.be.bignumber.equal(await token.totalSupply()); 466 | startingMMBalance.should.be.bignumber.equal(await mm.initialWei()); 467 | initialBuyPrice.should.be.bignumber.equal(await mm.initialBuyPrice()); 468 | 469 | const startTimestamp = parseInt(await mm.startTimestamp.call()); 470 | 471 | let state = { 472 | MVMMonth: 0, 473 | MVMPeriods: periods, 474 | MVMStartTimestamp: startTimestamp, 475 | MVMInitialBuyPrice: initialBuyPrice, 476 | token: token, 477 | initialTokenSupply: help.lif2LifWei(tokenTotalSupply), 478 | crowdsaleData: { 479 | foundationWallet: foundationWallet, 480 | }, 481 | foundationWallet: foundationWalletIndex, 482 | MVMBurnedTokens: new BigNumber(0), // burned tokens in MM, via sendTokens txs 483 | burnedTokens: new BigNumber(0), // total burned tokens, in MM or not (for compat with gen-test state) 484 | returnedWeiForBurnedTokens: new BigNumber(0), 485 | MVM: mm, 486 | MVMWeiBalance: startingMMBalance, 487 | MVMStartingBalance: startingMMBalance, 488 | MVMLifBalance: new BigNumber(0), 489 | ethBalances: help.getAccountsBalances(accounts), 490 | balances: {}, 491 | totalSupply: new BigNumber(0), 492 | initialBuyPrice: initialBuyPrice, 493 | MVMBuyPrice: initialBuyPrice, 494 | claimablePercentage: 0, 495 | MVMMaxClaimableWei: new BigNumber(0), 496 | MVMClaimedWei: new BigNumber(0), 497 | MVMPausedSeconds: new BigNumber(0), 498 | }; 499 | state.ethBalances[customerAddressIndex] = web3.eth.getBalance(customer); 500 | state.balances[customerAddressIndex] = await token.balanceOf(customer); 501 | 502 | assert.equal(foundationWallet, await mm.owner()); 503 | 504 | state.MVM = mm; 505 | 506 | let waitForMonth = async function (month) { 507 | await commands.commands.MVMWaitForMonth.run({ 508 | month: month, 509 | }, state); 510 | 511 | await checkScenarioProperties(state, mm, customer); 512 | }; 513 | 514 | // Month 0 515 | await waitForMonth(0); 516 | 517 | let sendTokens = async (tokens) => { 518 | await commands.commands.MVMSendTokens.run({ 519 | tokens: tokens, 520 | from: customerAddressIndex, 521 | }, state); 522 | await checkScenarioProperties(state, mm, customer); 523 | }; 524 | 525 | let claimWei = async (eth) => { 526 | await commands.commands.MVMClaimWei.run({ 527 | eth: eth, 528 | }, state); 529 | 530 | await checkScenarioProperties(state, mm, customer); 531 | }; 532 | 533 | // Sell 240 tokens to the MM 534 | await sendTokens(240); 535 | 536 | // Sell 480 tokens to the MM 537 | await sendTokens(480); 538 | 539 | // Month 1 540 | await waitForMonth(1); 541 | 542 | // Sell 240 tokens to the MM 543 | await sendTokens(240); 544 | 545 | let claimedWeiBeforeClaiming = state.MVMClaimedWei, 546 | maxClaimableBeforeClaiming = state.MVMMaxClaimableWei; 547 | assert(maxClaimableBeforeClaiming.gt(0)); 548 | 549 | // try to claim more than the max claimable and it should fail 550 | await claimWei(web3.fromWei(state.MVMMaxClaimableWei + 1)); 551 | assert.equal(claimedWeiBeforeClaiming, state.MVMClaimedWei, 552 | 'claimWei should have failed so claimedWei should have stayed the same'); 553 | 554 | // Claim all ether 555 | await claimWei(web3.fromWei(state.MVMMaxClaimableWei)); 556 | 557 | state.MVMClaimedWei.should.be.bignumber 558 | .equal(claimedWeiBeforeClaiming.plus(maxClaimableBeforeClaiming)); 559 | 560 | // Month 2 561 | await waitForMonth(2); 562 | 563 | // Sell 240 tokens to the MM 564 | await sendTokens(240); 565 | 566 | // Claim 18 ETH 567 | await claimWei(0.03); 568 | 569 | // Month 3 570 | await waitForMonth(3); 571 | 572 | // Sell 960 tokens to the MM 573 | await sendTokens(960); 574 | 575 | await waitForMonth(12); 576 | await waitForMonth(14); 577 | await waitForMonth(15); 578 | 579 | await claimWei(5); 580 | 581 | // Sell 240 tokens to the MM 582 | await sendTokens(240); 583 | 584 | new BigNumber(help.lif2LifWei(tokenTotalSupply)).minus(help.lif2LifWei(tokensInCrowdsale)) 585 | .should.be.bignumber.equal(await token.totalSupply.call()); 586 | 587 | await waitForMonth(25); 588 | 589 | (await web3.eth.getBalance(mm.address)).should.be.bignumber.gt(web3.toWei(0.3, 'ether')); 590 | 591 | help.debug('claiming remaining eth'); 592 | await claimWei(web3.fromWei(await web3.eth.getBalance(mm.address))); 593 | 594 | assert.equal(0, await web3.eth.getBalance(mm.address)); 595 | }); 596 | }); 597 | -------------------------------------------------------------------------------- /test/VestedPayment.js: -------------------------------------------------------------------------------- 1 | var help = require('./helpers'); 2 | var latestTime = require('./helpers/latestTime'); 3 | var { increaseTimeTestRPC, increaseTimeTestRPCTo, duration } = require('./helpers/increaseTime'); 4 | 5 | var BigNumber = web3.BigNumber; 6 | 7 | require('chai') 8 | .use(require('chai-bignumber')(BigNumber)) 9 | .should(); 10 | 11 | var VestedPayment = artifacts.require('./VestedPayment.sol'); 12 | var LifToken = artifacts.require('./LifToken.sol'); 13 | 14 | const LOG_EVENTS = true; 15 | 16 | contract('VestedPayment', function (accounts) { 17 | var token; 18 | var eventsWatcher; 19 | 20 | beforeEach(async function () { 21 | const rate = 100000000000; 22 | const crowdsale = await help.simulateCrowdsale(rate, [100], accounts, 1); 23 | token = LifToken.at(await crowdsale.token.call()); 24 | eventsWatcher = token.allEvents(); 25 | eventsWatcher.watch(function (error, log) { 26 | if (LOG_EVENTS) { 27 | if (error) { 28 | console.log('Error in event:', error); 29 | } else { 30 | console.log('Event:', log.event, ':', log.args); 31 | } 32 | } 33 | }); 34 | }); 35 | 36 | afterEach(function (done) { 37 | eventsWatcher.stopWatching(); 38 | done(); 39 | }); 40 | 41 | const oneMonth = duration.days(30); 42 | 43 | it('create the VestedPayment', async function () { 44 | const startTimestamp = latestTime() + duration.days(10); 45 | var vestedPayment = await VestedPayment.new( 46 | startTimestamp, oneMonth, 10, 4, 47 | 7, token.address 48 | ); 49 | assert.equal(startTimestamp, await vestedPayment.startTimestamp.call()); 50 | assert.equal(oneMonth, parseInt(await vestedPayment.secondsPerPeriod.call())); 51 | assert.equal(10, await vestedPayment.totalPeriods.call()); 52 | assert.equal(4, await vestedPayment.cliffDuration.call()); 53 | assert.equal(7, parseInt(await vestedPayment.tokens.call())); 54 | assert.equal(token.address, await vestedPayment.token.call()); 55 | }); 56 | 57 | it('can be created on the current timestamp', async function () { 58 | // we adds 2 seconds from the last block timestamp, because we don't know the exact timestamp 59 | // of the block this is going to be executeed on 60 | await VestedPayment.new(latestTime() + 2, oneMonth, 10, 4, 7, token.address); 61 | }); 62 | 63 | it('fails to create when startTimestamp is in the past', async function () { 64 | try { 65 | await VestedPayment.new(latestTime() - 1, oneMonth, 10, 4, 7, token.address); 66 | assert(false, 'create vested payment in the past should have failed'); 67 | } catch (e) { 68 | assert(help.isInvalidOpcodeEx(e)); 69 | } 70 | }); 71 | 72 | it('fails to create when secondsPerPeriod is 0', async function () { 73 | try { 74 | await VestedPayment.new(latestTime() + 2, 0, 10, 4, 7, token.address); 75 | assert(false, 'create vested payment with 0 as period duration should have failed'); 76 | } catch (e) { 77 | assert(help.isInvalidOpcodeEx(e)); 78 | } 79 | }); 80 | 81 | it('fails to create when total periods is 0', async function () { 82 | try { 83 | await VestedPayment.new(latestTime() + 2, oneMonth, 0, 4, 7, token.address); 84 | assert(false, 'create vested payment with 0 total periods should have failed'); 85 | } catch (e) { 86 | assert(help.isInvalidOpcodeEx(e)); 87 | } 88 | }); 89 | 90 | it('fails to create when token address is 0x0', async function () { 91 | try { 92 | await VestedPayment.new(latestTime() + 2, oneMonth, 10, 4, 7, help.zeroAddress); 93 | assert(false, 'create vested payment with address 0x0 should have failed'); 94 | } catch (e) { 95 | assert(help.isInvalidOpcodeEx(e)); 96 | } 97 | }); 98 | 99 | it('fails to create when cliff duration is longer than periods count', async function () { 100 | try { 101 | await VestedPayment.new(latestTime() + 7, oneMonth, 10, 11, 7, token.address); 102 | assert(false, 'create vested payment with too long cliff duration should have failed'); 103 | } catch (e) { 104 | assert(help.isInvalidOpcodeEx(e)); 105 | } 106 | }); 107 | 108 | it('fails to create when tokens is equal to 0', async function () { 109 | try { 110 | await VestedPayment.new(latestTime() + 7, oneMonth, 10, 4, 0, token.address); 111 | assert(false, 'create vested payment with too long cliff duration should have failed'); 112 | } catch (e) { 113 | assert(help.isInvalidOpcodeEx(e)); 114 | } 115 | }); 116 | 117 | it('get availableToClaim correctly with cliff periods', async function () { 118 | const startTimestamp = latestTime() + 2 * oneMonth; 119 | var vestedPayment = await VestedPayment.new( 120 | startTimestamp, oneMonth, 12, 4, 121 | help.lif2LifWei(60), token.address, { from: accounts[1] } 122 | ); 123 | var tokensAvailable = new BigNumber(0); 124 | 125 | await token.transfer(vestedPayment.address, help.lif2LifWei(60), { from: accounts[1] }); 126 | 127 | // Go to start 128 | await increaseTimeTestRPCTo(startTimestamp); 129 | 130 | tokensAvailable.should.be.bignumber 131 | .equal(await vestedPayment.getAvailableTokens.call()); 132 | 133 | // Go to period 2 134 | await increaseTimeTestRPC(duration.days(60)); 135 | 136 | tokensAvailable.should.be.bignumber 137 | .equal(await vestedPayment.getAvailableTokens.call()); 138 | 139 | // Go to period 4 140 | await increaseTimeTestRPC(duration.days(60)); 141 | 142 | tokensAvailable = tokensAvailable.plus(help.lif2LifWei(25)); 143 | 144 | tokensAvailable.should.be.bignumber 145 | .equal(await vestedPayment.getAvailableTokens.call()); 146 | }); 147 | 148 | it('get availableToClaim correctly without cliff periods', async function () { 149 | const startTimestamp = latestTime() + duration.days(60); 150 | var vestedPayment = await VestedPayment.new( 151 | startTimestamp, duration.days(30), 12, 0, 152 | help.lif2LifWei(60), token.address, { from: accounts[1] } 153 | ); 154 | var tokensAvailable = new BigNumber(0); 155 | 156 | await token.transfer(vestedPayment.address, help.lif2LifWei(60), { from: accounts[1] }); 157 | // Go to start 158 | await increaseTimeTestRPCTo(startTimestamp); 159 | 160 | tokensAvailable = tokensAvailable.plus(help.lif2LifWei(5)); 161 | 162 | tokensAvailable.should.be.bignumber 163 | .equal(await vestedPayment.getAvailableTokens.call()); 164 | 165 | // Go to period 5 166 | await increaseTimeTestRPC(duration.days(150)); 167 | 168 | tokensAvailable = tokensAvailable.plus(help.lif2LifWei(25)); 169 | 170 | tokensAvailable.should.be.bignumber 171 | .equal(await vestedPayment.getAvailableTokens.call()); 172 | }); 173 | 174 | it('claim tokens correctly with cliff periods and owner transfered', async function () { 175 | const startTimestamp = latestTime() + duration.days(60); 176 | var vestedPayment = await VestedPayment.new( 177 | startTimestamp, duration.days(30), 12, 4, 178 | help.lif2LifWei(60), token.address, { from: accounts[1] }); 179 | var tokensAvailable = new BigNumber(0); 180 | 181 | await token.transfer(vestedPayment.address, help.lif2LifWei(60), { from: accounts[1] }); 182 | await vestedPayment.transferOwnership(accounts[2], { from: accounts[1] }); 183 | assert.equal(accounts[2], await vestedPayment.owner.call()); 184 | 185 | // Go to start 186 | await increaseTimeTestRPCTo(startTimestamp); 187 | 188 | tokensAvailable.should.be.bignumber 189 | .equal(await vestedPayment.getAvailableTokens.call()); 190 | 191 | // Go to period 4 and claim 20 tokens 192 | await increaseTimeTestRPCTo(startTimestamp + duration.days(120)); 193 | 194 | tokensAvailable = tokensAvailable.plus(help.lif2LifWei(25)); 195 | 196 | tokensAvailable.should.be.bignumber 197 | .equal(await vestedPayment.getAvailableTokens.call()); 198 | 199 | await vestedPayment.claimTokens(help.lif2LifWei(20), { from: accounts[2] }); 200 | tokensAvailable = tokensAvailable.minus(help.lif2LifWei(20)); 201 | 202 | tokensAvailable.should.be.bignumber 203 | .equal(await vestedPayment.getAvailableTokens.call()); 204 | 205 | // Go to period 10 and claim 10 tokens 206 | await increaseTimeTestRPCTo(startTimestamp + duration.days(300)); 207 | 208 | tokensAvailable = tokensAvailable.plus(help.lif2LifWei(30)); 209 | 210 | tokensAvailable.should.be.bignumber 211 | .equal(await vestedPayment.getAvailableTokens.call()); 212 | 213 | await vestedPayment.claimTokens(help.lif2LifWei(10), { from: accounts[2] }); 214 | tokensAvailable = tokensAvailable.minus(help.lif2LifWei(10)); 215 | 216 | tokensAvailable.should.be.bignumber 217 | .equal(await vestedPayment.getAvailableTokens.call()); 218 | 219 | // Go to period 11 plus 90 days and claim teh remaining tokens 220 | await increaseTimeTestRPCTo(startTimestamp + duration.days(320) + duration.days(90)); 221 | 222 | tokensAvailable = tokensAvailable.plus(help.lif2LifWei(5)); 223 | 224 | tokensAvailable.should.be.bignumber 225 | .equal(await vestedPayment.getAvailableTokens.call()); 226 | 227 | await vestedPayment.claimTokens(tokensAvailable, { from: accounts[2] }); 228 | 229 | help.lif2LifWei(60).should.be.bignumber 230 | .equal(await token.balanceOf(accounts[2])); 231 | }); 232 | 233 | it('should fail when try to claim tokens inside cliff periods', async function () { 234 | const startTimestamp = latestTime() + duration.days(60); 235 | var vestedPayment = await VestedPayment.new( 236 | startTimestamp, duration.days(30), 12, 4, 237 | help.lif2LifWei(60), token.address, { from: accounts[1] } 238 | ); 239 | 240 | await token.transfer(vestedPayment.address, help.lif2LifWei(60), { from: accounts[1] }); 241 | await vestedPayment.transferOwnership(accounts[2], { from: accounts[1] }); 242 | 243 | assert.equal(accounts[2], await vestedPayment.owner.call()); 244 | 245 | // Go to period 1 246 | await increaseTimeTestRPCTo(startTimestamp + duration.days(30)); 247 | 248 | try { 249 | await vestedPayment.claimTokens(help.lif2LifWei(5), { from: accounts[2] }); 250 | } catch (error) { 251 | if (!help.isInvalidOpcodeEx(error)) throw error; 252 | } 253 | }); 254 | 255 | it('should fail when try to claim more tokens than the available', async function () { 256 | const startTimestamp = latestTime() + duration.days(60); 257 | var vestedPayment = await VestedPayment.new( 258 | startTimestamp, duration.days(30), 12, 0, 259 | help.lif2LifWei(60), token.address, { from: accounts[1] } 260 | ); 261 | 262 | await token.transfer(vestedPayment.address, help.lif2LifWei(60), { from: accounts[1] }); 263 | await vestedPayment.transferOwnership(accounts[2], { from: accounts[1] }); 264 | 265 | assert.equal(accounts[2], await vestedPayment.owner.call()); 266 | 267 | // Go to period 1 268 | await increaseTimeTestRPCTo(startTimestamp + duration.days(30)); 269 | 270 | try { 271 | await vestedPayment.claimTokens(help.lif2LifWei(15), { from: accounts[2] }); 272 | } catch (error) { 273 | if (!help.isInvalidOpcodeEx(error)) throw error; 274 | } 275 | }); 276 | }); 277 | -------------------------------------------------------------------------------- /test/deploy/TGEDeployer.js: -------------------------------------------------------------------------------- 1 | var LifCrowdsale = artifacts.require('./LifCrowdsale.sol'), 2 | LifToken = artifacts.require('./LifToken.sol'), 3 | TGEDeployer = artifacts.require('./deploy/TGEDeployer.sol'); 4 | 5 | let help = require('../helpers'); 6 | 7 | var BigNumber = web3.BigNumber; 8 | 9 | require('chai') 10 | .use(require('chai-bignumber')(BigNumber)) 11 | .should(); 12 | 13 | var { duration } = require('../helpers/increaseTime'); 14 | const _ = require('lodash'); 15 | 16 | const TGEDistribution = require('./TGEDistribution'); 17 | 18 | contract('TGE Deployer', function ([deployAddress, foundationWallet, foundersWallet]) { 19 | it.skip('Deploy a TGE correctly', async function () { 20 | const startTimestamp = new Date('1 Feb 2018 08:00:00 GMT').getTime() / 1000; 21 | const end1Timestamp = new Date('7 Feb 2018 08:00:00 GMT').getTime() / 1000; 22 | const end2Timestamp = new Date('14 Feb 2018 24:00:00 GMT').getTime() / 1000; 23 | const rate1 = 1000; 24 | const rate2 = 900; 25 | const setWeiLockSeconds = duration.hours(1); 26 | var totalSupply = new BigNumber(0); 27 | var ETHRaised = new BigNumber(0); 28 | const USDperETH = 1150; 29 | 30 | // Use this rate factor to matxh the starting USD raised to 1.5M USD 31 | const RATE_FACTOR = 4.05; 32 | const weiPerUSDinTGE = web3.toWei(1 / USDperETH); 33 | 34 | // Override foundation and founders wallet 35 | foundationWallet = '0xDAD697274F95F909ad12437C516626d65263Ce47'; 36 | foundersWallet = '0xe7dfDF4b7Dd5e9BD0bb94d59379219B625A6433d '; 37 | 38 | const deployer = await TGEDeployer.new( 39 | startTimestamp, end1Timestamp, end2Timestamp, rate1, 40 | rate2, setWeiLockSeconds, foundationWallet, foundersWallet, 41 | { from: deployAddress } 42 | ); 43 | 44 | const crowdsale = LifCrowdsale.at(await deployer.crowdsale()); 45 | const token = LifToken.at(await crowdsale.token()); 46 | 47 | console.log('TGE Deployer deploy parameters:', startTimestamp, end1Timestamp, 48 | end2Timestamp, rate1, rate2, setWeiLockSeconds, foundationWallet, foundersWallet); 49 | help.debug('Data to create Deployer contract:'); 50 | help.debug(web3.eth.getTransaction(deployer.contract.transactionHash).input); 51 | help.debug('--------------------------------------------------'); 52 | 53 | // Check values 54 | assert.equal(startTimestamp, parseInt(await crowdsale.startTimestamp.call())); 55 | assert.equal(end1Timestamp, parseInt(await crowdsale.end1Timestamp.call())); 56 | assert.equal(end2Timestamp, parseInt(await crowdsale.end2Timestamp.call())); 57 | assert.equal(rate1, parseInt(await crowdsale.rate1.call())); 58 | assert.equal(rate2, parseInt(await crowdsale.rate2.call())); 59 | assert.equal(foundationWallet, parseInt(await crowdsale.foundationWallet.call())); 60 | assert.equal(foundersWallet, parseInt(await crowdsale.foundersWallet.call())); 61 | 62 | var processStage = async function (stage) { 63 | // Check right amount of contributos and values 64 | assert.equal(stage.contributors.length, stage.values.length); 65 | var stageETH = new BigNumber(0); 66 | stage.rate = new BigNumber(stage.rate).mul(RATE_FACTOR); 67 | 68 | // Parse ETH to wei 69 | stage.values.map(function (value, i) { 70 | stage.values[i] = new BigNumber(stage.values[i]).div(RATE_FACTOR); 71 | ETHRaised = ETHRaised.add(stage.values[i]); 72 | stageETH = stageETH.add(stage.values[i]); 73 | stage.values[i] = web3.toWei(stage.values[i]); 74 | }); 75 | 76 | // Add TGE stage 77 | const contributorsChunks = _.chunk(stage.contributors, 150); 78 | const valuesChunks = _.chunk(stage.values, 150); 79 | var txs = []; 80 | for (var i = 0; i < contributorsChunks.length; i++) { 81 | const data = await deployer.contract.addPresaleTokens.getData( 82 | contributorsChunks[i], valuesChunks[i], stage.rate 83 | ); 84 | await deployer.addPresaleTokens( 85 | contributorsChunks[i], valuesChunks[i], stage.rate, 86 | { from: deployAddress } 87 | ); 88 | txs.push(data); 89 | } 90 | 91 | // Calculate tokens and check total supply 92 | const stageTokens = new BigNumber(stageETH).mul(stage.rate); 93 | totalSupply = totalSupply.add(stageTokens); 94 | help.debug('TXs for stage', stage.name); 95 | txs.map(function (tx, i) { help.debug('TX [', i, ']', tx); }); 96 | help.debug('--------------------------------------------------'); 97 | totalSupply.should.be.bignumber 98 | .equal(help.lifWei2Lif(await token.totalSupply()), 2); 99 | }; 100 | 101 | await processStage(TGEDistribution[0]); 102 | await processStage(TGEDistribution[1]); 103 | await processStage(TGEDistribution[2]); 104 | await processStage(TGEDistribution[3]); 105 | await processStage(TGEDistribution[4]); 106 | 107 | const finalizeData = await deployer.contract.finish.getData(weiPerUSDinTGE); 108 | 109 | await deployer.finish(weiPerUSDinTGE, { from: deployAddress }); 110 | 111 | help.debug('Data to finalize Deployer:'); 112 | help.debug(finalizeData); 113 | help.debug('--------------------------------------------------'); 114 | 115 | assert.equal(foundationWallet, parseInt(await crowdsale.owner.call())); 116 | assert.equal(parseInt(weiPerUSDinTGE), parseInt(await crowdsale.weiPerUSDinTGE.call())); 117 | 118 | const USDRaised = ETHRaised * USDperETH; 119 | console.log('USD per ETH price in TGE:', USDperETH); 120 | console.log('USD raised:', USDRaised); 121 | console.log('ETH raised:', parseFloat(ETHRaised).toFixed(8)); 122 | 123 | // // Check USD raised 124 | new BigNumber(parseFloat(USDRaised).toFixed(2)).should.be.bignumber 125 | .equal((await crowdsale.weiRaised()).div(weiPerUSDinTGE), 2); 126 | 127 | // Check ETH raised 128 | ETHRaised.should.be.bignumber 129 | .equal(web3.fromWei(await crowdsale.weiRaised()), 2); 130 | 131 | // Check final total supply 132 | totalSupply.should.be.bignumber 133 | .equal(help.lifWei2Lif(await token.totalSupply()), 2); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/deploy/deployed_contracts_build/ERC20.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "ERC20", 3 | "abi": [ 4 | { 5 | "constant": false, 6 | "inputs": [ 7 | { 8 | "name": "spender", 9 | "type": "address" 10 | }, 11 | { 12 | "name": "value", 13 | "type": "uint256" 14 | } 15 | ], 16 | "name": "approve", 17 | "outputs": [ 18 | { 19 | "name": "", 20 | "type": "bool" 21 | } 22 | ], 23 | "payable": false, 24 | "stateMutability": "nonpayable", 25 | "type": "function" 26 | }, 27 | { 28 | "constant": true, 29 | "inputs": [], 30 | "name": "totalSupply", 31 | "outputs": [ 32 | { 33 | "name": "", 34 | "type": "uint256" 35 | } 36 | ], 37 | "payable": false, 38 | "stateMutability": "view", 39 | "type": "function" 40 | }, 41 | { 42 | "constant": false, 43 | "inputs": [ 44 | { 45 | "name": "from", 46 | "type": "address" 47 | }, 48 | { 49 | "name": "to", 50 | "type": "address" 51 | }, 52 | { 53 | "name": "value", 54 | "type": "uint256" 55 | } 56 | ], 57 | "name": "transferFrom", 58 | "outputs": [ 59 | { 60 | "name": "", 61 | "type": "bool" 62 | } 63 | ], 64 | "payable": false, 65 | "stateMutability": "nonpayable", 66 | "type": "function" 67 | }, 68 | { 69 | "constant": true, 70 | "inputs": [ 71 | { 72 | "name": "who", 73 | "type": "address" 74 | } 75 | ], 76 | "name": "balanceOf", 77 | "outputs": [ 78 | { 79 | "name": "", 80 | "type": "uint256" 81 | } 82 | ], 83 | "payable": false, 84 | "stateMutability": "view", 85 | "type": "function" 86 | }, 87 | { 88 | "constant": false, 89 | "inputs": [ 90 | { 91 | "name": "to", 92 | "type": "address" 93 | }, 94 | { 95 | "name": "value", 96 | "type": "uint256" 97 | } 98 | ], 99 | "name": "transfer", 100 | "outputs": [ 101 | { 102 | "name": "", 103 | "type": "bool" 104 | } 105 | ], 106 | "payable": false, 107 | "stateMutability": "nonpayable", 108 | "type": "function" 109 | }, 110 | { 111 | "constant": true, 112 | "inputs": [ 113 | { 114 | "name": "owner", 115 | "type": "address" 116 | }, 117 | { 118 | "name": "spender", 119 | "type": "address" 120 | } 121 | ], 122 | "name": "allowance", 123 | "outputs": [ 124 | { 125 | "name": "", 126 | "type": "uint256" 127 | } 128 | ], 129 | "payable": false, 130 | "stateMutability": "view", 131 | "type": "function" 132 | }, 133 | { 134 | "anonymous": false, 135 | "inputs": [ 136 | { 137 | "indexed": true, 138 | "name": "owner", 139 | "type": "address" 140 | }, 141 | { 142 | "indexed": true, 143 | "name": "spender", 144 | "type": "address" 145 | }, 146 | { 147 | "indexed": false, 148 | "name": "value", 149 | "type": "uint256" 150 | } 151 | ], 152 | "name": "Approval", 153 | "type": "event" 154 | }, 155 | { 156 | "anonymous": false, 157 | "inputs": [ 158 | { 159 | "indexed": true, 160 | "name": "from", 161 | "type": "address" 162 | }, 163 | { 164 | "indexed": true, 165 | "name": "to", 166 | "type": "address" 167 | }, 168 | { 169 | "indexed": false, 170 | "name": "value", 171 | "type": "uint256" 172 | } 173 | ], 174 | "name": "Transfer", 175 | "type": "event" 176 | } 177 | ], 178 | "bytecode": "0x", 179 | "deployedBytecode": "0x", 180 | "sourceMap": "", 181 | "deployedSourceMap": "", 182 | "source": "pragma solidity ^0.4.18;\n\nimport \"./ERC20Basic.sol\";\n\n\n/**\n * @title ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/20\n */\ncontract ERC20 is ERC20Basic {\n function allowance(address owner, address spender) public view returns (uint256);\n function transferFrom(address from, address to, uint256 value) public returns (bool);\n function approve(address spender, uint256 value) public returns (bool);\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n", 183 | "sourcePath": "zeppelin-solidity/contracts/token/ERC20/ERC20.sol", 184 | "ast": { 185 | "attributes": { 186 | "absolutePath": "zeppelin-solidity/contracts/token/ERC20/ERC20.sol", 187 | "exportedSymbols": { 188 | "ERC20": [ 189 | 3283 190 | ] 191 | } 192 | }, 193 | "children": [ 194 | { 195 | "attributes": { 196 | "literals": [ 197 | "solidity", 198 | "^", 199 | "0.4", 200 | ".18" 201 | ] 202 | }, 203 | "id": 3242, 204 | "name": "PragmaDirective", 205 | "src": "0:24:15" 206 | }, 207 | { 208 | "attributes": { 209 | "SourceUnit": 3316, 210 | "absolutePath": "zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol", 211 | "file": "./ERC20Basic.sol", 212 | "scope": 3284, 213 | "symbolAliases": [ 214 | null 215 | ], 216 | "unitAlias": "" 217 | }, 218 | "id": 3243, 219 | "name": "ImportDirective", 220 | "src": "26:26:15" 221 | }, 222 | { 223 | "attributes": { 224 | "contractDependencies": [ 225 | 3315 226 | ], 227 | "contractKind": "contract", 228 | "documentation": "@title ERC20 interface\n@dev see https://github.com/ethereum/EIPs/issues/20", 229 | "fullyImplemented": false, 230 | "linearizedBaseContracts": [ 231 | 3283, 232 | 3315 233 | ], 234 | "name": "ERC20", 235 | "scope": 3284 236 | }, 237 | "children": [ 238 | { 239 | "attributes": { 240 | "arguments": [ 241 | null 242 | ] 243 | }, 244 | "children": [ 245 | { 246 | "attributes": { 247 | "contractScope": null, 248 | "name": "ERC20Basic", 249 | "referencedDeclaration": 3315, 250 | "type": "contract ERC20Basic" 251 | }, 252 | "id": 3244, 253 | "name": "UserDefinedTypeName", 254 | "src": "162:10:15" 255 | } 256 | ], 257 | "id": 3245, 258 | "name": "InheritanceSpecifier", 259 | "src": "162:10:15" 260 | }, 261 | { 262 | "attributes": { 263 | "body": null, 264 | "constant": true, 265 | "implemented": false, 266 | "isConstructor": false, 267 | "modifiers": [ 268 | null 269 | ], 270 | "name": "allowance", 271 | "payable": false, 272 | "scope": 3283, 273 | "stateMutability": "view", 274 | "superFunction": null, 275 | "visibility": "public" 276 | }, 277 | "children": [ 278 | { 279 | "children": [ 280 | { 281 | "attributes": { 282 | "constant": false, 283 | "name": "owner", 284 | "scope": 3254, 285 | "stateVariable": false, 286 | "storageLocation": "default", 287 | "type": "address", 288 | "value": null, 289 | "visibility": "internal" 290 | }, 291 | "children": [ 292 | { 293 | "attributes": { 294 | "name": "address", 295 | "type": "address" 296 | }, 297 | "id": 3246, 298 | "name": "ElementaryTypeName", 299 | "src": "196:7:15" 300 | } 301 | ], 302 | "id": 3247, 303 | "name": "VariableDeclaration", 304 | "src": "196:13:15" 305 | }, 306 | { 307 | "attributes": { 308 | "constant": false, 309 | "name": "spender", 310 | "scope": 3254, 311 | "stateVariable": false, 312 | "storageLocation": "default", 313 | "type": "address", 314 | "value": null, 315 | "visibility": "internal" 316 | }, 317 | "children": [ 318 | { 319 | "attributes": { 320 | "name": "address", 321 | "type": "address" 322 | }, 323 | "id": 3248, 324 | "name": "ElementaryTypeName", 325 | "src": "211:7:15" 326 | } 327 | ], 328 | "id": 3249, 329 | "name": "VariableDeclaration", 330 | "src": "211:15:15" 331 | } 332 | ], 333 | "id": 3250, 334 | "name": "ParameterList", 335 | "src": "195:32:15" 336 | }, 337 | { 338 | "children": [ 339 | { 340 | "attributes": { 341 | "constant": false, 342 | "name": "", 343 | "scope": 3254, 344 | "stateVariable": false, 345 | "storageLocation": "default", 346 | "type": "uint256", 347 | "value": null, 348 | "visibility": "internal" 349 | }, 350 | "children": [ 351 | { 352 | "attributes": { 353 | "name": "uint256", 354 | "type": "uint256" 355 | }, 356 | "id": 3251, 357 | "name": "ElementaryTypeName", 358 | "src": "249:7:15" 359 | } 360 | ], 361 | "id": 3252, 362 | "name": "VariableDeclaration", 363 | "src": "249:7:15" 364 | } 365 | ], 366 | "id": 3253, 367 | "name": "ParameterList", 368 | "src": "248:9:15" 369 | } 370 | ], 371 | "id": 3254, 372 | "name": "FunctionDefinition", 373 | "src": "177:81:15" 374 | }, 375 | { 376 | "attributes": { 377 | "body": null, 378 | "constant": false, 379 | "implemented": false, 380 | "isConstructor": false, 381 | "modifiers": [ 382 | null 383 | ], 384 | "name": "transferFrom", 385 | "payable": false, 386 | "scope": 3283, 387 | "stateMutability": "nonpayable", 388 | "superFunction": null, 389 | "visibility": "public" 390 | }, 391 | "children": [ 392 | { 393 | "children": [ 394 | { 395 | "attributes": { 396 | "constant": false, 397 | "name": "from", 398 | "scope": 3265, 399 | "stateVariable": false, 400 | "storageLocation": "default", 401 | "type": "address", 402 | "value": null, 403 | "visibility": "internal" 404 | }, 405 | "children": [ 406 | { 407 | "attributes": { 408 | "name": "address", 409 | "type": "address" 410 | }, 411 | "id": 3255, 412 | "name": "ElementaryTypeName", 413 | "src": "283:7:15" 414 | } 415 | ], 416 | "id": 3256, 417 | "name": "VariableDeclaration", 418 | "src": "283:12:15" 419 | }, 420 | { 421 | "attributes": { 422 | "constant": false, 423 | "name": "to", 424 | "scope": 3265, 425 | "stateVariable": false, 426 | "storageLocation": "default", 427 | "type": "address", 428 | "value": null, 429 | "visibility": "internal" 430 | }, 431 | "children": [ 432 | { 433 | "attributes": { 434 | "name": "address", 435 | "type": "address" 436 | }, 437 | "id": 3257, 438 | "name": "ElementaryTypeName", 439 | "src": "297:7:15" 440 | } 441 | ], 442 | "id": 3258, 443 | "name": "VariableDeclaration", 444 | "src": "297:10:15" 445 | }, 446 | { 447 | "attributes": { 448 | "constant": false, 449 | "name": "value", 450 | "scope": 3265, 451 | "stateVariable": false, 452 | "storageLocation": "default", 453 | "type": "uint256", 454 | "value": null, 455 | "visibility": "internal" 456 | }, 457 | "children": [ 458 | { 459 | "attributes": { 460 | "name": "uint256", 461 | "type": "uint256" 462 | }, 463 | "id": 3259, 464 | "name": "ElementaryTypeName", 465 | "src": "309:7:15" 466 | } 467 | ], 468 | "id": 3260, 469 | "name": "VariableDeclaration", 470 | "src": "309:13:15" 471 | } 472 | ], 473 | "id": 3261, 474 | "name": "ParameterList", 475 | "src": "282:41:15" 476 | }, 477 | { 478 | "children": [ 479 | { 480 | "attributes": { 481 | "constant": false, 482 | "name": "", 483 | "scope": 3265, 484 | "stateVariable": false, 485 | "storageLocation": "default", 486 | "type": "bool", 487 | "value": null, 488 | "visibility": "internal" 489 | }, 490 | "children": [ 491 | { 492 | "attributes": { 493 | "name": "bool", 494 | "type": "bool" 495 | }, 496 | "id": 3262, 497 | "name": "ElementaryTypeName", 498 | "src": "340:4:15" 499 | } 500 | ], 501 | "id": 3263, 502 | "name": "VariableDeclaration", 503 | "src": "340:4:15" 504 | } 505 | ], 506 | "id": 3264, 507 | "name": "ParameterList", 508 | "src": "339:6:15" 509 | } 510 | ], 511 | "id": 3265, 512 | "name": "FunctionDefinition", 513 | "src": "261:85:15" 514 | }, 515 | { 516 | "attributes": { 517 | "body": null, 518 | "constant": false, 519 | "implemented": false, 520 | "isConstructor": false, 521 | "modifiers": [ 522 | null 523 | ], 524 | "name": "approve", 525 | "payable": false, 526 | "scope": 3283, 527 | "stateMutability": "nonpayable", 528 | "superFunction": null, 529 | "visibility": "public" 530 | }, 531 | "children": [ 532 | { 533 | "children": [ 534 | { 535 | "attributes": { 536 | "constant": false, 537 | "name": "spender", 538 | "scope": 3274, 539 | "stateVariable": false, 540 | "storageLocation": "default", 541 | "type": "address", 542 | "value": null, 543 | "visibility": "internal" 544 | }, 545 | "children": [ 546 | { 547 | "attributes": { 548 | "name": "address", 549 | "type": "address" 550 | }, 551 | "id": 3266, 552 | "name": "ElementaryTypeName", 553 | "src": "366:7:15" 554 | } 555 | ], 556 | "id": 3267, 557 | "name": "VariableDeclaration", 558 | "src": "366:15:15" 559 | }, 560 | { 561 | "attributes": { 562 | "constant": false, 563 | "name": "value", 564 | "scope": 3274, 565 | "stateVariable": false, 566 | "storageLocation": "default", 567 | "type": "uint256", 568 | "value": null, 569 | "visibility": "internal" 570 | }, 571 | "children": [ 572 | { 573 | "attributes": { 574 | "name": "uint256", 575 | "type": "uint256" 576 | }, 577 | "id": 3268, 578 | "name": "ElementaryTypeName", 579 | "src": "383:7:15" 580 | } 581 | ], 582 | "id": 3269, 583 | "name": "VariableDeclaration", 584 | "src": "383:13:15" 585 | } 586 | ], 587 | "id": 3270, 588 | "name": "ParameterList", 589 | "src": "365:32:15" 590 | }, 591 | { 592 | "children": [ 593 | { 594 | "attributes": { 595 | "constant": false, 596 | "name": "", 597 | "scope": 3274, 598 | "stateVariable": false, 599 | "storageLocation": "default", 600 | "type": "bool", 601 | "value": null, 602 | "visibility": "internal" 603 | }, 604 | "children": [ 605 | { 606 | "attributes": { 607 | "name": "bool", 608 | "type": "bool" 609 | }, 610 | "id": 3271, 611 | "name": "ElementaryTypeName", 612 | "src": "414:4:15" 613 | } 614 | ], 615 | "id": 3272, 616 | "name": "VariableDeclaration", 617 | "src": "414:4:15" 618 | } 619 | ], 620 | "id": 3273, 621 | "name": "ParameterList", 622 | "src": "413:6:15" 623 | } 624 | ], 625 | "id": 3274, 626 | "name": "FunctionDefinition", 627 | "src": "349:71:15" 628 | }, 629 | { 630 | "attributes": { 631 | "anonymous": false, 632 | "name": "Approval" 633 | }, 634 | "children": [ 635 | { 636 | "children": [ 637 | { 638 | "attributes": { 639 | "constant": false, 640 | "indexed": true, 641 | "name": "owner", 642 | "scope": 3282, 643 | "stateVariable": false, 644 | "storageLocation": "default", 645 | "type": "address", 646 | "value": null, 647 | "visibility": "internal" 648 | }, 649 | "children": [ 650 | { 651 | "attributes": { 652 | "name": "address", 653 | "type": "address" 654 | }, 655 | "id": 3275, 656 | "name": "ElementaryTypeName", 657 | "src": "438:7:15" 658 | } 659 | ], 660 | "id": 3276, 661 | "name": "VariableDeclaration", 662 | "src": "438:21:15" 663 | }, 664 | { 665 | "attributes": { 666 | "constant": false, 667 | "indexed": true, 668 | "name": "spender", 669 | "scope": 3282, 670 | "stateVariable": false, 671 | "storageLocation": "default", 672 | "type": "address", 673 | "value": null, 674 | "visibility": "internal" 675 | }, 676 | "children": [ 677 | { 678 | "attributes": { 679 | "name": "address", 680 | "type": "address" 681 | }, 682 | "id": 3277, 683 | "name": "ElementaryTypeName", 684 | "src": "461:7:15" 685 | } 686 | ], 687 | "id": 3278, 688 | "name": "VariableDeclaration", 689 | "src": "461:23:15" 690 | }, 691 | { 692 | "attributes": { 693 | "constant": false, 694 | "indexed": false, 695 | "name": "value", 696 | "scope": 3282, 697 | "stateVariable": false, 698 | "storageLocation": "default", 699 | "type": "uint256", 700 | "value": null, 701 | "visibility": "internal" 702 | }, 703 | "children": [ 704 | { 705 | "attributes": { 706 | "name": "uint256", 707 | "type": "uint256" 708 | }, 709 | "id": 3279, 710 | "name": "ElementaryTypeName", 711 | "src": "486:7:15" 712 | } 713 | ], 714 | "id": 3280, 715 | "name": "VariableDeclaration", 716 | "src": "486:13:15" 717 | } 718 | ], 719 | "id": 3281, 720 | "name": "ParameterList", 721 | "src": "437:63:15" 722 | } 723 | ], 724 | "id": 3282, 725 | "name": "EventDefinition", 726 | "src": "423:78:15" 727 | } 728 | ], 729 | "id": 3283, 730 | "name": "ContractDefinition", 731 | "src": "144:359:15" 732 | } 733 | ], 734 | "id": 3284, 735 | "name": "SourceUnit", 736 | "src": "0:504:15" 737 | }, 738 | "compiler": { 739 | "name": "solc", 740 | "version": "0.4.18+commit.9cf6e910.Emscripten.clang" 741 | }, 742 | "networks": {}, 743 | "schemaVersion": "1.0.1", 744 | "updatedAt": "2018-01-30T01:37:56.671Z" 745 | } -------------------------------------------------------------------------------- /test/deploy/deployed_contracts_build/ERC20Basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "ERC20Basic", 3 | "abi": [ 4 | { 5 | "constant": true, 6 | "inputs": [], 7 | "name": "totalSupply", 8 | "outputs": [ 9 | { 10 | "name": "", 11 | "type": "uint256" 12 | } 13 | ], 14 | "payable": false, 15 | "stateMutability": "view", 16 | "type": "function" 17 | }, 18 | { 19 | "constant": true, 20 | "inputs": [ 21 | { 22 | "name": "who", 23 | "type": "address" 24 | } 25 | ], 26 | "name": "balanceOf", 27 | "outputs": [ 28 | { 29 | "name": "", 30 | "type": "uint256" 31 | } 32 | ], 33 | "payable": false, 34 | "stateMutability": "view", 35 | "type": "function" 36 | }, 37 | { 38 | "constant": false, 39 | "inputs": [ 40 | { 41 | "name": "to", 42 | "type": "address" 43 | }, 44 | { 45 | "name": "value", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "transfer", 50 | "outputs": [ 51 | { 52 | "name": "", 53 | "type": "bool" 54 | } 55 | ], 56 | "payable": false, 57 | "stateMutability": "nonpayable", 58 | "type": "function" 59 | }, 60 | { 61 | "anonymous": false, 62 | "inputs": [ 63 | { 64 | "indexed": true, 65 | "name": "from", 66 | "type": "address" 67 | }, 68 | { 69 | "indexed": true, 70 | "name": "to", 71 | "type": "address" 72 | }, 73 | { 74 | "indexed": false, 75 | "name": "value", 76 | "type": "uint256" 77 | } 78 | ], 79 | "name": "Transfer", 80 | "type": "event" 81 | } 82 | ], 83 | "bytecode": "0x", 84 | "deployedBytecode": "0x", 85 | "sourceMap": "", 86 | "deployedSourceMap": "", 87 | "source": "pragma solidity ^0.4.18;\n\n\n/**\n * @title ERC20Basic\n * @dev Simpler version of ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/179\n */\ncontract ERC20Basic {\n function totalSupply() public view returns (uint256);\n function balanceOf(address who) public view returns (uint256);\n function transfer(address to, uint256 value) public returns (bool);\n event Transfer(address indexed from, address indexed to, uint256 value);\n}\n", 88 | "sourcePath": "zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol", 89 | "ast": { 90 | "attributes": { 91 | "absolutePath": "zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol", 92 | "exportedSymbols": { 93 | "ERC20Basic": [ 94 | 3315 95 | ] 96 | } 97 | }, 98 | "children": [ 99 | { 100 | "attributes": { 101 | "literals": [ 102 | "solidity", 103 | "^", 104 | "0.4", 105 | ".18" 106 | ] 107 | }, 108 | "id": 3285, 109 | "name": "PragmaDirective", 110 | "src": "0:24:16" 111 | }, 112 | { 113 | "attributes": { 114 | "baseContracts": [ 115 | null 116 | ], 117 | "contractDependencies": [ 118 | null 119 | ], 120 | "contractKind": "contract", 121 | "documentation": "@title ERC20Basic\n@dev Simpler version of ERC20 interface\n@dev see https://github.com/ethereum/EIPs/issues/179", 122 | "fullyImplemented": false, 123 | "linearizedBaseContracts": [ 124 | 3315 125 | ], 126 | "name": "ERC20Basic", 127 | "scope": 3316 128 | }, 129 | "children": [ 130 | { 131 | "attributes": { 132 | "body": null, 133 | "constant": true, 134 | "implemented": false, 135 | "isConstructor": false, 136 | "modifiers": [ 137 | null 138 | ], 139 | "name": "totalSupply", 140 | "payable": false, 141 | "scope": 3315, 142 | "stateMutability": "view", 143 | "superFunction": null, 144 | "visibility": "public" 145 | }, 146 | "children": [ 147 | { 148 | "attributes": { 149 | "parameters": [ 150 | null 151 | ] 152 | }, 153 | "children": [], 154 | "id": 3286, 155 | "name": "ParameterList", 156 | "src": "199:2:16" 157 | }, 158 | { 159 | "children": [ 160 | { 161 | "attributes": { 162 | "constant": false, 163 | "name": "", 164 | "scope": 3290, 165 | "stateVariable": false, 166 | "storageLocation": "default", 167 | "type": "uint256", 168 | "value": null, 169 | "visibility": "internal" 170 | }, 171 | "children": [ 172 | { 173 | "attributes": { 174 | "name": "uint256", 175 | "type": "uint256" 176 | }, 177 | "id": 3287, 178 | "name": "ElementaryTypeName", 179 | "src": "223:7:16" 180 | } 181 | ], 182 | "id": 3288, 183 | "name": "VariableDeclaration", 184 | "src": "223:7:16" 185 | } 186 | ], 187 | "id": 3289, 188 | "name": "ParameterList", 189 | "src": "222:9:16" 190 | } 191 | ], 192 | "id": 3290, 193 | "name": "FunctionDefinition", 194 | "src": "179:53:16" 195 | }, 196 | { 197 | "attributes": { 198 | "body": null, 199 | "constant": true, 200 | "implemented": false, 201 | "isConstructor": false, 202 | "modifiers": [ 203 | null 204 | ], 205 | "name": "balanceOf", 206 | "payable": false, 207 | "scope": 3315, 208 | "stateMutability": "view", 209 | "superFunction": null, 210 | "visibility": "public" 211 | }, 212 | "children": [ 213 | { 214 | "children": [ 215 | { 216 | "attributes": { 217 | "constant": false, 218 | "name": "who", 219 | "scope": 3297, 220 | "stateVariable": false, 221 | "storageLocation": "default", 222 | "type": "address", 223 | "value": null, 224 | "visibility": "internal" 225 | }, 226 | "children": [ 227 | { 228 | "attributes": { 229 | "name": "address", 230 | "type": "address" 231 | }, 232 | "id": 3291, 233 | "name": "ElementaryTypeName", 234 | "src": "254:7:16" 235 | } 236 | ], 237 | "id": 3292, 238 | "name": "VariableDeclaration", 239 | "src": "254:11:16" 240 | } 241 | ], 242 | "id": 3293, 243 | "name": "ParameterList", 244 | "src": "253:13:16" 245 | }, 246 | { 247 | "children": [ 248 | { 249 | "attributes": { 250 | "constant": false, 251 | "name": "", 252 | "scope": 3297, 253 | "stateVariable": false, 254 | "storageLocation": "default", 255 | "type": "uint256", 256 | "value": null, 257 | "visibility": "internal" 258 | }, 259 | "children": [ 260 | { 261 | "attributes": { 262 | "name": "uint256", 263 | "type": "uint256" 264 | }, 265 | "id": 3294, 266 | "name": "ElementaryTypeName", 267 | "src": "288:7:16" 268 | } 269 | ], 270 | "id": 3295, 271 | "name": "VariableDeclaration", 272 | "src": "288:7:16" 273 | } 274 | ], 275 | "id": 3296, 276 | "name": "ParameterList", 277 | "src": "287:9:16" 278 | } 279 | ], 280 | "id": 3297, 281 | "name": "FunctionDefinition", 282 | "src": "235:62:16" 283 | }, 284 | { 285 | "attributes": { 286 | "body": null, 287 | "constant": false, 288 | "implemented": false, 289 | "isConstructor": false, 290 | "modifiers": [ 291 | null 292 | ], 293 | "name": "transfer", 294 | "payable": false, 295 | "scope": 3315, 296 | "stateMutability": "nonpayable", 297 | "superFunction": null, 298 | "visibility": "public" 299 | }, 300 | "children": [ 301 | { 302 | "children": [ 303 | { 304 | "attributes": { 305 | "constant": false, 306 | "name": "to", 307 | "scope": 3306, 308 | "stateVariable": false, 309 | "storageLocation": "default", 310 | "type": "address", 311 | "value": null, 312 | "visibility": "internal" 313 | }, 314 | "children": [ 315 | { 316 | "attributes": { 317 | "name": "address", 318 | "type": "address" 319 | }, 320 | "id": 3298, 321 | "name": "ElementaryTypeName", 322 | "src": "318:7:16" 323 | } 324 | ], 325 | "id": 3299, 326 | "name": "VariableDeclaration", 327 | "src": "318:10:16" 328 | }, 329 | { 330 | "attributes": { 331 | "constant": false, 332 | "name": "value", 333 | "scope": 3306, 334 | "stateVariable": false, 335 | "storageLocation": "default", 336 | "type": "uint256", 337 | "value": null, 338 | "visibility": "internal" 339 | }, 340 | "children": [ 341 | { 342 | "attributes": { 343 | "name": "uint256", 344 | "type": "uint256" 345 | }, 346 | "id": 3300, 347 | "name": "ElementaryTypeName", 348 | "src": "330:7:16" 349 | } 350 | ], 351 | "id": 3301, 352 | "name": "VariableDeclaration", 353 | "src": "330:13:16" 354 | } 355 | ], 356 | "id": 3302, 357 | "name": "ParameterList", 358 | "src": "317:27:16" 359 | }, 360 | { 361 | "children": [ 362 | { 363 | "attributes": { 364 | "constant": false, 365 | "name": "", 366 | "scope": 3306, 367 | "stateVariable": false, 368 | "storageLocation": "default", 369 | "type": "bool", 370 | "value": null, 371 | "visibility": "internal" 372 | }, 373 | "children": [ 374 | { 375 | "attributes": { 376 | "name": "bool", 377 | "type": "bool" 378 | }, 379 | "id": 3303, 380 | "name": "ElementaryTypeName", 381 | "src": "361:4:16" 382 | } 383 | ], 384 | "id": 3304, 385 | "name": "VariableDeclaration", 386 | "src": "361:4:16" 387 | } 388 | ], 389 | "id": 3305, 390 | "name": "ParameterList", 391 | "src": "360:6:16" 392 | } 393 | ], 394 | "id": 3306, 395 | "name": "FunctionDefinition", 396 | "src": "300:67:16" 397 | }, 398 | { 399 | "attributes": { 400 | "anonymous": false, 401 | "name": "Transfer" 402 | }, 403 | "children": [ 404 | { 405 | "children": [ 406 | { 407 | "attributes": { 408 | "constant": false, 409 | "indexed": true, 410 | "name": "from", 411 | "scope": 3314, 412 | "stateVariable": false, 413 | "storageLocation": "default", 414 | "type": "address", 415 | "value": null, 416 | "visibility": "internal" 417 | }, 418 | "children": [ 419 | { 420 | "attributes": { 421 | "name": "address", 422 | "type": "address" 423 | }, 424 | "id": 3307, 425 | "name": "ElementaryTypeName", 426 | "src": "385:7:16" 427 | } 428 | ], 429 | "id": 3308, 430 | "name": "VariableDeclaration", 431 | "src": "385:20:16" 432 | }, 433 | { 434 | "attributes": { 435 | "constant": false, 436 | "indexed": true, 437 | "name": "to", 438 | "scope": 3314, 439 | "stateVariable": false, 440 | "storageLocation": "default", 441 | "type": "address", 442 | "value": null, 443 | "visibility": "internal" 444 | }, 445 | "children": [ 446 | { 447 | "attributes": { 448 | "name": "address", 449 | "type": "address" 450 | }, 451 | "id": 3309, 452 | "name": "ElementaryTypeName", 453 | "src": "407:7:16" 454 | } 455 | ], 456 | "id": 3310, 457 | "name": "VariableDeclaration", 458 | "src": "407:18:16" 459 | }, 460 | { 461 | "attributes": { 462 | "constant": false, 463 | "indexed": false, 464 | "name": "value", 465 | "scope": 3314, 466 | "stateVariable": false, 467 | "storageLocation": "default", 468 | "type": "uint256", 469 | "value": null, 470 | "visibility": "internal" 471 | }, 472 | "children": [ 473 | { 474 | "attributes": { 475 | "name": "uint256", 476 | "type": "uint256" 477 | }, 478 | "id": 3311, 479 | "name": "ElementaryTypeName", 480 | "src": "427:7:16" 481 | } 482 | ], 483 | "id": 3312, 484 | "name": "VariableDeclaration", 485 | "src": "427:13:16" 486 | } 487 | ], 488 | "id": 3313, 489 | "name": "ParameterList", 490 | "src": "384:57:16" 491 | } 492 | ], 493 | "id": 3314, 494 | "name": "EventDefinition", 495 | "src": "370:72:16" 496 | } 497 | ], 498 | "id": 3315, 499 | "name": "ContractDefinition", 500 | "src": "155:289:16" 501 | } 502 | ], 503 | "id": 3316, 504 | "name": "SourceUnit", 505 | "src": "0:445:16" 506 | }, 507 | "compiler": { 508 | "name": "solc", 509 | "version": "0.4.18+commit.9cf6e910.Emscripten.clang" 510 | }, 511 | "networks": {}, 512 | "schemaVersion": "1.0.1", 513 | "updatedAt": "2018-01-30T01:37:56.672Z" 514 | } -------------------------------------------------------------------------------- /test/generators.js: -------------------------------------------------------------------------------- 1 | var jsc = require('jsverify'); 2 | 3 | var help = require('./helpers'); 4 | 5 | // this is just to have web3 available and correctly initialized 6 | artifacts.require('./LifToken.sol'); 7 | 8 | const knownAccountGen = jsc.nat(web3.eth.accounts.length - 1); 9 | const zeroAddressAccountGen = jsc.constant('zero'); 10 | const accountGen = jsc.oneof([zeroAddressAccountGen, knownAccountGen]); 11 | 12 | function getAccount (account) { 13 | if (account === 'zero') { 14 | return help.zeroAddress; 15 | } else { 16 | return web3.eth.accounts[account]; 17 | } 18 | } 19 | 20 | module.exports = { 21 | 22 | accountGen: accountGen, 23 | 24 | getAccount: getAccount, 25 | 26 | crowdsaleGen: jsc.record({ 27 | rate1: jsc.nat, 28 | rate2: jsc.nat, 29 | foundationWallet: knownAccountGen, 30 | foundersWallet: knownAccountGen, 31 | setWeiLockSeconds: jsc.integer(600, 3600), 32 | owner: knownAccountGen, 33 | }), 34 | 35 | waitBlockCommandGen: jsc.record({ 36 | type: jsc.constant('waitBlock'), 37 | blocks: jsc.nat, 38 | }), 39 | 40 | waitTimeCommandGen: jsc.record({ 41 | type: jsc.constant('waitTime'), 42 | seconds: jsc.nat, 43 | }), 44 | 45 | checkRateCommandGen: jsc.record({ 46 | type: jsc.constant('checkRate'), 47 | }), 48 | 49 | setWeiPerUSDinTGECommandGen: jsc.record({ 50 | type: jsc.constant('setWeiPerUSDinTGE'), 51 | wei: jsc.integer(0, 10000000000000000), // between 0-0.01 ETH 52 | fromAccount: accountGen, 53 | }), 54 | 55 | buyTokensCommandGen: jsc.record({ 56 | type: jsc.constant('buyTokens'), 57 | account: accountGen, 58 | beneficiary: accountGen, 59 | eth: jsc.nat, 60 | }), 61 | 62 | burnTokensCommandGen: jsc.record({ 63 | type: jsc.constant('burnTokens'), 64 | account: accountGen, 65 | tokens: jsc.nat, 66 | }), 67 | 68 | sendTransactionCommandGen: jsc.record({ 69 | type: jsc.constant('sendTransaction'), 70 | account: accountGen, 71 | beneficiary: accountGen, 72 | eth: jsc.nat, 73 | }), 74 | 75 | pauseCrowdsaleCommandGen: jsc.record({ 76 | type: jsc.constant('pauseCrowdsale'), 77 | pause: jsc.bool, 78 | fromAccount: accountGen, 79 | }), 80 | 81 | pauseTokenCommandGen: jsc.record({ 82 | type: jsc.constant('pauseToken'), 83 | pause: jsc.bool, 84 | fromAccount: accountGen, 85 | }), 86 | 87 | finalizeCrowdsaleCommandGen: jsc.record({ 88 | type: jsc.constant('finalizeCrowdsale'), 89 | fromAccount: accountGen, 90 | }), 91 | 92 | addPrivatePresalePaymentCommandGen: jsc.record({ 93 | type: jsc.constant('addPrivatePresalePayment'), 94 | beneficiaryAccount: accountGen, 95 | fromAccount: accountGen, 96 | eth: jsc.nat, 97 | rate: jsc.integer(10, 200), 98 | }), 99 | 100 | claimEthCommandGen: jsc.record({ 101 | type: jsc.constant('claimEth'), 102 | eth: jsc.nat, 103 | fromAccount: accountGen, 104 | }), 105 | 106 | returnPurchaseCommandGen: jsc.record({ 107 | type: jsc.constant('returnPurchase'), 108 | eth: jsc.nat, 109 | fromAccount: accountGen, 110 | contributor: accountGen, 111 | }), 112 | 113 | transferCommandGen: jsc.record({ 114 | type: jsc.constant('transfer'), 115 | lif: jsc.nat, 116 | fromAccount: accountGen, 117 | toAccount: accountGen, 118 | }), 119 | 120 | approveCommandGen: jsc.record({ 121 | type: jsc.constant('approve'), 122 | lif: jsc.nat, 123 | fromAccount: accountGen, 124 | spenderAccount: accountGen, 125 | }), 126 | 127 | transferFromCommandGen: jsc.record({ 128 | type: jsc.constant('transferFrom'), 129 | lif: jsc.nat, 130 | senderAccount: accountGen, 131 | fromAccount: accountGen, 132 | toAccount: accountGen, 133 | }), 134 | 135 | MVMSendTokensCommandGen: jsc.record({ 136 | type: jsc.constant('MVMSendTokens'), 137 | tokens: jsc.nat, 138 | from: accountGen, 139 | }), 140 | 141 | MVMClaimWeiCommandGen: jsc.record({ 142 | type: jsc.constant('MVMClaimWei'), 143 | eth: jsc.nat, 144 | }), 145 | 146 | MVMWaitForMonthCommandGen: jsc.record({ 147 | type: jsc.constant('MVMWaitForMonth'), 148 | month: jsc.nat, 149 | }), 150 | 151 | MVMPauseCommandGen: jsc.record({ 152 | type: jsc.constant('MVMPause'), 153 | pause: jsc.bool, 154 | fromAccount: knownAccountGen, 155 | }), 156 | 157 | fundCrowdsaleBelowMinCap: jsc.record({ 158 | type: jsc.constant('fundCrowdsaleBelowSoftCap'), 159 | account: knownAccountGen, // we don't want this one to fail with 0x0 addresses 160 | fundingEth: jsc.nat, 161 | finalize: jsc.bool, 162 | }), 163 | 164 | fundCrowdsaleBelowSoftCap: jsc.record({ 165 | type: jsc.constant('fundCrowdsaleBelowSoftCap'), 166 | account: knownAccountGen, // we don't want this one to fail with 0x0 addresses 167 | finalize: jsc.bool, 168 | }), 169 | 170 | fundCrowdsaleOverSoftCap: jsc.record({ 171 | type: jsc.constant('fundCrowdsaleOverSoftCap'), 172 | account: knownAccountGen, // we don't want this one to fail with 0x0 addresses 173 | softCapExcessWei: jsc.nat, 174 | finalize: jsc.bool, 175 | }), 176 | 177 | }; 178 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var BigNumber = web3.BigNumber; 2 | 3 | var _ = require('lodash'); 4 | 5 | var LifToken = artifacts.require('./LifToken.sol'); 6 | var LifCrowdsale = artifacts.require('./LifCrowdsale.sol'); 7 | var LifMarketValidationMechanism = artifacts.require('./LifMarketValidationMechanism.sol'); 8 | var abiDecoder = require('abi-decoder'); 9 | abiDecoder.addABI(LifToken._json.abi); 10 | abiDecoder.addABI(LifCrowdsale._json.abi); 11 | abiDecoder.addABI(LifMarketValidationMechanism._json.abi); 12 | 13 | var latestTime = require('./helpers/latestTime'); 14 | var { increaseTimeTestRPC, increaseTimeTestRPCTo } = require('./helpers/increaseTime'); 15 | 16 | const DEBUG_MODE = (process.env.WT_DEBUG === 'true') || false; 17 | 18 | let gasPriceFromEnv = parseInt(process.env.GAS_PRICE); 19 | let gasPrice; 20 | if (isNaN(gasPriceFromEnv)) { gasPrice = new BigNumber(20000000000); } else { gasPrice = new BigNumber(gasPriceFromEnv); } 21 | 22 | module.exports = { 23 | 24 | zeroAddress: '0x0000000000000000000000000000000000000000', 25 | 26 | abiDecoder: abiDecoder, 27 | 28 | inCoverage: () => process.env.SOLIDITY_COVERAGE === 'true', 29 | 30 | gasPrice: gasPrice, 31 | 32 | txGasCost: (tx) => gasPrice.mul(new BigNumber(tx.receipt.gasUsed)), 33 | 34 | getAccountsBalances: (accounts) => { 35 | return _.reduce(accounts, (balances, account) => { 36 | balances[accounts.indexOf(account)] = web3.eth.getBalance(account); 37 | return balances; 38 | }, {}); 39 | }, 40 | 41 | hexEncode: function (str) { 42 | var hex, i; 43 | var result = ''; 44 | for (i = 0; i < str.length; i++) { 45 | hex = str.charCodeAt(i).toString(16); 46 | result += ('000' + hex).slice(-4); 47 | } 48 | return result; 49 | }, 50 | 51 | hexDecode: function (str) { 52 | var j; 53 | var hexes = str.match(/.{1,4}/g) || []; 54 | var back = ''; 55 | for (j = 0; j < hexes.length; j++) { 56 | back += String.fromCharCode(parseInt(hexes[j], 16)); 57 | } 58 | return back; 59 | }, 60 | 61 | lifWei2Lif: function (value) { 62 | return web3.fromWei(value, 'ether'); 63 | }, 64 | 65 | lif2LifWei: function (value) { 66 | return web3.toWei(value, 'ether'); 67 | }, 68 | 69 | isInvalidOpcodeEx: function (e) { 70 | return ((e.message.search('invalid opcode') >= 0) || 71 | (e.message.search('revert') >= 0)); 72 | }, 73 | 74 | waitBlocks: function (toWait, accounts) { 75 | return this.waitToBlock(parseInt(web3.eth.blockNumber) + toWait, accounts); 76 | }, 77 | 78 | simulateCrowdsale: async function (rate, balances, accounts, weiPerUSD) { 79 | await increaseTimeTestRPC(1); 80 | var startTime = latestTime() + 5; 81 | var endTime = startTime + 20; 82 | var crowdsale = await LifCrowdsale.new( 83 | startTime + 3, startTime + 15, endTime, 84 | rate, rate + 10, 1, 85 | accounts[0], accounts[1] 86 | ); 87 | await increaseTimeTestRPCTo(latestTime() + 1); 88 | await crowdsale.setWeiPerUSDinTGE(weiPerUSD); 89 | await increaseTimeTestRPCTo(startTime + 3); 90 | for (let i = 0; i < 5; i++) { 91 | if (balances[i] > 0) { await crowdsale.sendTransaction({ value: web3.toWei(balances[i] / rate, 'ether'), from: accounts[i + 1] }); } 92 | } 93 | await increaseTimeTestRPCTo(endTime + 1); 94 | await crowdsale.finalize(true); 95 | let token = LifToken.at(await crowdsale.token()); 96 | await token.unpause(); 97 | return crowdsale; 98 | }, 99 | 100 | debug: DEBUG_MODE ? console.log : function () {}, 101 | 102 | checkToken: async function (token, accounts, totalSupply, balances) { 103 | let debug = this.debug; 104 | let [ 105 | tokenTotalSupply, 106 | tokenAccountBalances, 107 | ] = await Promise.all([ 108 | token.totalSupply(), 109 | Promise.all([ 110 | token.balanceOf(accounts[1]), 111 | token.balanceOf(accounts[2]), 112 | token.balanceOf(accounts[3]), 113 | token.balanceOf(accounts[4]), 114 | token.balanceOf(accounts[5]), 115 | ]), 116 | ]); 117 | 118 | debug('Total Supply:', this.lifWei2Lif(parseFloat(tokenTotalSupply))); 119 | for (let i = 0; i < 5; i++) { 120 | debug( 121 | 'Account[' + (i + 1) + ']', 122 | accounts[i + 1], 123 | ', Balance:', this.lifWei2Lif(tokenAccountBalances[i]) 124 | ); 125 | } 126 | 127 | if (totalSupply) { assert.equal(this.lifWei2Lif(parseFloat(tokenTotalSupply)), totalSupply); } 128 | if (balances) { 129 | assert.equal(this.lifWei2Lif(tokenAccountBalances[0]), balances[0]); 130 | assert.equal(this.lifWei2Lif(tokenAccountBalances[1]), balances[1]); 131 | assert.equal(this.lifWei2Lif(tokenAccountBalances[2]), balances[2]); 132 | assert.equal(this.lifWei2Lif(tokenAccountBalances[3]), balances[3]); 133 | assert.equal(this.lifWei2Lif(tokenAccountBalances[4]), balances[4]); 134 | } 135 | }, 136 | 137 | getCrowdsaleExpectedRate: function (crowdsale, time) { 138 | let { 139 | publicPresaleStartTimestamp, publicPresaleEndTimestamp, startTimestamp, 140 | end1Timestamp, end2Timestamp, publicPresaleRate, rate1, rate2 } = crowdsale; 141 | 142 | if (time < publicPresaleStartTimestamp) { 143 | return 0; 144 | } else if (time <= publicPresaleEndTimestamp) { 145 | return publicPresaleRate; 146 | } else if (time < startTimestamp) { 147 | return 0; 148 | } else if (time <= end1Timestamp) { 149 | return rate1; 150 | } else if (time <= end2Timestamp) { 151 | return rate2; 152 | } else { 153 | return 0; 154 | } 155 | }, 156 | 157 | getPresalePaymentMaxTokens: function (minCap, maxTokens, presaleBonusRate, presaleAmountEth) { 158 | let minTokenPrice = minCap / maxTokens; 159 | return (presaleAmountEth / minTokenPrice) * (presaleBonusRate + 100) / 100; 160 | }, 161 | }; 162 | -------------------------------------------------------------------------------- /test/helpers/increaseTime.js: -------------------------------------------------------------------------------- 1 | var latestTime = require('./latestTime'); 2 | 3 | // Increases testrpc time by the passed duration in seconds 4 | 5 | function increaseTimeTestRPC (duration) { 6 | const id = Date.now(); 7 | 8 | return new Promise((resolve, reject) => { 9 | web3.currentProvider.sendAsync({ 10 | jsonrpc: '2.0', 11 | method: 'evm_increaseTime', 12 | params: [duration], 13 | id: id, 14 | }, err1 => { 15 | if (err1) return reject(err1); 16 | 17 | web3.currentProvider.sendAsync({ 18 | jsonrpc: '2.0', 19 | method: 'evm_mine', 20 | id: id + 1, 21 | }, (err2, res) => { 22 | return err2 ? reject(err2) : resolve(res); 23 | }); 24 | }); 25 | }); 26 | } 27 | 28 | function increaseTimeTestRPCTo (target) { 29 | let now = latestTime(); 30 | if (target < now) throw Error(`Cannot increase current time(${now}) to a moment in the past(${target})`); 31 | let diff = target - now; 32 | return increaseTimeTestRPC(diff); 33 | } 34 | 35 | module.exports = { 36 | 37 | /** 38 | * Beware that due to the need of calling two separate testrpc methods and rpc calls overhead 39 | * it's hard to increase time precisely to a target point so design your test to tolerate 40 | * small fluctuations from time to time. 41 | * 42 | * @param target time in seconds 43 | */ 44 | increaseTimeTestRPC: increaseTimeTestRPC, 45 | increaseTimeTestRPCTo: increaseTimeTestRPCTo, 46 | duration: { 47 | seconds: function (val) { return val; }, 48 | minutes: function (val) { return val * this.seconds(60); }, 49 | hours: function (val) { return val * this.minutes(60); }, 50 | days: function (val) { return val * this.hours(24); }, 51 | weeks: function (val) { return val * this.days(7); }, 52 | years: function (val) { return val * this.days(365); }, 53 | }, 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /test/helpers/latestTime.js: -------------------------------------------------------------------------------- 1 | // Returns the time of the last mined block in seconds 2 | module.exports = function latestTime () { 3 | return web3.eth.getBlock('latest').timestamp; 4 | }; 5 | -------------------------------------------------------------------------------- /tokenList.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Winding Tree", 3 | "timestamp": "2021-05-12T15:58:56+01:00", 4 | "version": { 5 | "major": 2, 6 | "minor": 1, 7 | "patch": 0 8 | }, 9 | "tags": { 10 | }, 11 | "logoURI": "https://raw.githubusercontent.com/windingtree/branding/master/winding-tree/png/winding-tree-symbol-small.png", 12 | "keywords": [ 13 | "travel" 14 | ], 15 | "tokens": [ 16 | { 17 | "name": "Lif", 18 | "address": "0xEB9951021698B42e4399f9cBb6267Aa35F82D59D", 19 | "symbol": "LIF", 20 | "decimals": 18, 21 | "chainId": 1, 22 | "logoURI": "https://raw.githubusercontent.com/windingtree/branding/master/lif/png/lif-logo.png" 23 | }, 24 | { 25 | "name": "Lif", 26 | "address": "0xB6e225194a1C892770c43D4B529841C99b3DA1d7", 27 | "symbol": "LIF", 28 | "decimals": 18, 29 | "chainId": 3, 30 | "logoURI": "https://raw.githubusercontent.com/windingtree/branding/master/lif/png/lif-logo.png" 31 | }, 32 | { 33 | "name": "Lif", 34 | "address": "0x5b3455590Ec7F5a25119885Ed62aFBA9bC2e5D65", 35 | "symbol": "LIF", 36 | "decimals": 18, 37 | "chainId": 137, 38 | "logoURI": "https://raw.githubusercontent.com/windingtree/branding/master/lif/png/lif-logo.png" 39 | }, 40 | { 41 | "name": "Wrapped Ether", 42 | "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 43 | "symbol": "WETH", 44 | "decimals": 18, 45 | "chainId": 1, 46 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" 47 | }, 48 | { 49 | "name": "Wrapped Ether", 50 | "address": "0xc778417E063141139Fce010982780140Aa0cD5Ab", 51 | "symbol": "WETH", 52 | "decimals": 18, 53 | "chainId": 3, 54 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc778417E063141139Fce010982780140Aa0cD5Ab/logo.png" 55 | }, 56 | { 57 | "name": "Wrapped Ether", 58 | "address": "0xc778417E063141139Fce010982780140Aa0cD5Ab", 59 | "symbol": "WETH", 60 | "decimals": 18, 61 | "chainId": 4, 62 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc778417E063141139Fce010982780140Aa0cD5Ab/logo.png" 63 | }, 64 | { 65 | "name": "Wrapped Ether", 66 | "address": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", 67 | "symbol": "WETH", 68 | "decimals": 18, 69 | "chainId": 5, 70 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/logo.png" 71 | }, 72 | { 73 | "name": "Wrapped Ether", 74 | "address": "0xd0A1E359811322d97991E03f863a0C30C2cF029C", 75 | "symbol": "WETH", 76 | "decimals": 18, 77 | "chainId": 42, 78 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xd0A1E359811322d97991E03f863a0C30C2cF029C/logo.png" 79 | }, 80 | { 81 | "name": "Ether", 82 | "address": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", 83 | "symbol": "ETH", 84 | "decimals": 18, 85 | "chainId": 137, 86 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" 87 | }, 88 | { 89 | "name": "Dai Stablecoin", 90 | "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", 91 | "symbol": "DAI", 92 | "decimals": 18, 93 | "chainId": 1, 94 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png" 95 | }, 96 | { 97 | "name": "Dai Stablecoin", 98 | "address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", 99 | "symbol": "DAI", 100 | "decimals": 18, 101 | "chainId": 137, 102 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png" 103 | }, 104 | { 105 | "name": "USDCoin", 106 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 107 | "symbol": "USDC", 108 | "decimals": 6, 109 | "chainId": 1, 110 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" 111 | }, 112 | { 113 | "name": "USDCoin", 114 | "address": "0x07865c6e87b9f70255377e024ace6630c1eaa37f", 115 | "symbol": "USDC", 116 | "decimals": 6, 117 | "chainId": 3, 118 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" 119 | }, 120 | { 121 | "name": "USCoin", 122 | "address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", 123 | "symbol": "USDC", 124 | "decimals": 6, 125 | "chainId": 137, 126 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" 127 | }, 128 | { 129 | "name": "Tether USD", 130 | "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", 131 | "symbol": "USDT", 132 | "decimals": 6, 133 | "chainId": 1, 134 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png" 135 | }, 136 | { 137 | "name": "Tether USD", 138 | "address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", 139 | "symbol": "USDT", 140 | "decimals": 6, 141 | "chainId": 137, 142 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png" 143 | }, 144 | { 145 | "name": "Wrapped BTC", 146 | "address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", 147 | "symbol": "WBTC", 148 | "decimals": 8, 149 | "chainId": 1, 150 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png" 151 | }, 152 | { 153 | "name": "Wrapped BTC", 154 | "address": "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", 155 | "symbol": "WBTC", 156 | "decimals": 8, 157 | "chainId": 137, 158 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png" 159 | } 160 | ] 161 | } 162 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | var HDWalletProvider = require('truffle-hdwallet-provider'); 2 | 3 | var mnemonic = '[REDACTED]'; 4 | 5 | if (!process.env.SOLIDITY_COVERAGE){ 6 | // This is a stub to use in case you begin validating on a testnet using HDWallet. 7 | // HDWallet interferes with the coverage runner so it needs to be instantiated conditionally. 8 | // For more info see the solidity-coverage FAQ. 9 | // 10 | // provider = new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/') 11 | } 12 | 13 | module.exports = { 14 | networks: { 15 | development: { 16 | host: 'localhost', 17 | port: 8545, 18 | network_id: '*', // eslint-disable-line camelcase 19 | gas: 8000000, 20 | gasPrice: 41000000000, 21 | }, 22 | coverage: { 23 | host: 'localhost', 24 | network_id: '*', // eslint-disable-line camelcase 25 | port: 8555, 26 | gas: 0xfffffffffff, 27 | gasPrice: 0x01, 28 | }, 29 | }, 30 | solc: { 31 | optimizer: { 32 | enabled: true, 33 | runs: 200 34 | } 35 | }, 36 | mocha: { 37 | // a commented out mocha option, shows how to pass mocha options 38 | // bail: true // bail makes mocha to stop as soon as a test failure is found 39 | } 40 | }; 41 | --------------------------------------------------------------------------------