(Created Mar 08 2017. Updated with test results Mar 14 2017. Updated with multisig wallet contribution test results Mar 23 2017.)
I was asked to review the Sikoba presale smart contracts before the upcoming Sikoba presale crowdfunding event. This page documents Bok Consulting’s general and security audit of Sikoba’s presale smart contracts.
Summary
- There was only one major recommendation to fix the original contract to remove the situation where the owner’s ethers can get locked up within the contract.
- All other recommendations and changes have been to improve the readability of the source code. The third and final revision is listed below.
- The contract has been tested with
geth 1.5.9-stable
andsolc 0.4.9+commit.364da425.Darwin.appleclang
and work as intended. The results are listed below. - The final tested version of the contract can be found at https://github.com/sikoba/token-presale/blob/f4cf1ab95e7b18cf0612076968342a4a4e1087f3/contracts/SikobaPresale.sol.
- There may be deployment gas limit issues if there are too many preallocation entries to add to the contract constructor. The alternative
fill(...)
andseal()
method can be used to work around this potential gas limit issue, but this procedure will require separate testing before deployment. - There is a small possibility that the deployed source cannot be verified on etherscan.io . The deployment of this contract can only be done once as the preallocation ethers will be sent along with the contract. It is advisable to first try deploying a test contract with a small preallocation amount and then verifying the source on etherscan.io.
References
Table of contents
- 1. The Original Contracts
- 2. The First Revision SikobaPresale.sol Contract
- 3. The Second Revision SikobaPresale.sol Contract
- 4. The Third And Final Revision SikobaPresale.sol Contract
- 5. Testing Multisig Wallet Contributions To The SikobaPresale Contract
1. The Original Contracts
The first version of the source code is located at https://github.com/sikoba/token-presale/commit/74a237d8da0d4c39e4bc90c3aa3b244910e4afd9.
Following are the original contracts with my additional comments marked as:
// CHECK:
for the check done// RECOMMENDATION:
for any recommendations// FORMAT:
for recommended code reformatting for easier reading// ACTION:
for areas that require action
From the first round of reviews, there is only one important recommendation:
- Fix the logic within
withdraw(...)
as the owner can accidentally end up with a situation where ethers can be trapped in this contract.
Lower priority, I would format the source code including the comments as a document for potential investors to read. There is some renaming that can be done to it easier for readers to understand, e.g., MINIMAL_BALANCE_OF_PRESALE -> PRESALE_MINIMUM_BALANCE and valueInWei -> value.
1.1 owned.sol
From https://github.com/sikoba/token-presale/blob/master/contracts/owned.sol:
owned.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// DO NOT use the newest version of solidity but an old stable one, check the bug lists of in the release anouncement of solidity after this versions. pragma solidity ^0.4.8; // RECOMMENDATION: Rename 'owned' to 'Owned' for naming convention consistency // FORMAT: Remove blank lines after onlyowner to formatting consistency contract owned { address public owner; // RECOMMENDATION: Set the owner variable to the owner in this contract so this contract // is fully functional by itself, and not have to depend on the // derived contract having to set the variable // function Owned() { // owner = msg.sender; // } // RECOMMENDATION: Rename to 'onlyowner' to 'onlyOwner' for naming convention consistency modifier onlyowner() { if (msg.sender == owner) { _; } } // RECOMMENDATION: Replace onlyowner() above with one that throws an exception. // Ethereum Wallet will warn the user that the execution of a function // with this modifier will fail, if not run by the owner. Non-owners // would otherwise waste gas // modifier onlyOwner() { // if (msg.sender != owner) { // throw; // } // _; // } } |
1.2 SikobaPresale.sol
From https://github.com/sikoba/token-presale/blob/master/contracts/SikobaPresale.sol:
SikobaPresale.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
// DO NOT use the newest version of solidity but an old stable one, // check the bug lists of in the release anouncement of solidity after this versions. pragma solidity ^0.4.8; /** * SIKOBA PRESALE CONTRACTS * * Version 0.1 * * Author Roland Kofler, Alex Kampa * * MIT LICENSE Copyright 2016 Sikoba LTD * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. **/ // RECOMMENDATION: Include the following in this source code file. It's a very small bit of // additional code and makes it easier to check both contracts // Also easier for people to paste into Remix and test it out import "./owned.sol"; /// @title Sikoba Presale Contract /// @author Roland Kofler, Alex Kampa /// @dev changes to this contract will invalidate any security audits done before. It is MANDATORY to protocol audits in the "Security reviews done" section /// # Security checklists to use in each review: /// - Consensys checklist https://github.com/ConsenSys/smart-contract-best-practices /// - Roland Kofler's checklist https://github.com/rolandkofler/ether-security /// - read all of the code and use creative and lateral thinking to disccover bugs /// # Security reviews done: /// Date Auditors Short summary of the review executed /// 2017 MAR 3 - Roland Kofler - NO SECURITY REVIEW DONE /// 2017 MAR 7 - Roland Kofler, Alex Kampa - informal Security Review, added overflow protections, fixed wrong inequality operatiors, added maximal amount per transactions /// 2017 MAR 7 - Alex Kampa - some code clean up, removed restriction of MINIMAL_AMOUNT_TO_SEND for preallocations // RECOMMENDATION: owned -> Owned // RECOMMENDATION: Use uint256 instead of uint. Makes it certain so the user does not have to think. And // the user will not have to convert these to uint when working out function signatures, // eg: web3.sha3('withdraw(uint256)').substring(0, 10) = "0x2e1a7d4d" // RECOMMENDATION: There is the use of valueInWei and value in different functions and this is meant to be // the same kind of value. Rename valueInWei to value for consistency. // FORMAT: I would capitalise the comments as it makes it easier to pick out the sentences. We want // other users to read this, so let's make it easier // FORMAT: Some of your comments run far off the page. Pick something like 80 or 120 columns. Makes // it easier to read the comments. // FORMAT: I would treat this source code including the comments like a Word document that you are // formatting for people to read, and just neaten up the comments contract SikobaPresale is owned { //TODO: substitute all wei values with ether values before launching, if they are set for not spending too much on ropsten // RECOMMENDATION: MINIMAL -> MINIMUM, MAXIMAL -> MAXIMUM as MINIMUM and MAXIMUM are more hard limits // Minimal and maximal amounts per transaction for public participants //TODO: check if for test reasons there is no wrong amount uint public constant MINIMAL_AMOUNT_TO_SEND = 5 ether; uint public constant MAXIMAL_AMOUNT_TO_SEND = 250 ether; // RECOMMENDATION: MINIMAL -> MINIMUM, MAXIMAL -> MAXIMUM as MINIMUM and MAXIMUM are more hard limits // RECOMMENDATION: MINIMAL_BALANCE_OF_PRESALE -> PRESALE_MINIMUM_BALANCE - easier to read // RECOMMENDATION: MAXIMAL_BALANCE_OF_PRESALE -> PRESALE_MAXIMUM_BALANCE - easier to read // Minimal and maximal goals of the presale // If the minimum is not reached at the end of the presale, senders can withdraw via withdrawYourAssetsIfPresaleFailed() //TODO: before launch, adjust these values to approximate the desired EUR at current rates uint public constant MINIMAL_BALANCE_OF_PRESALE = 7000 ether; uint public constant MAXIMAL_BALANCE_OF_PRESALE = 14000 ether; // CHECK: 1491393600 -> GMT: Tue, 07 Mar 2017 22:38:04 GMT // From https://www.epochconverter.com/?TimeStamp=1491393600 // The constant is set to a testing date/time, you may want to include the real constant // in a comment so it could have been double checked // Public presale period // Starts on 04/05/2017 @ 12:00pm (UTC) 2017-04-05T12:00:00+00:00 in ISO 8601 // Ends 2 weeks after the start // RECOMMENDATION: START_DATE_PRESALE -> PRESALE_START_DATE - easier to read // RECOMMENDATION: DEADLINE_DATE_PRESALE -> PRESALE_END_DATE - easier to read uint public constant START_DATE_PRESALE = 1491393600; // FORMAT: Remove space before semicolon uint public constant DEADLINE_DATE_PRESALE = START_DATE_PRESALE + 2 weeks ; /// @notice The balances of all submitted Ether, includes preallocated Ether and the Ether submitted during the public phase /// @dev name complies with ERC20 token standard, etherscan for example will recognize this and show the balances of the address // CHECK: Ok mapping (address => uint) public balanceOf; /// @notice a log of participations in the presale and preallocation /// @dev helps to extract the addresses form balanceOf. /// Because we want to avoid loops to prevent 'out of gas' runtime bugs we /// don't hold a set of unique participants but simply log partecipations. // CHECK: Ok event LogParticipation(address indexed sender, uint value, uint timestamp, bool isPreallocation); /// @dev creating the contract needs two steps: /// 1. Set the value each preallocation address has submitted to the Sikoba bookmaker /// 2. Send the total amount of preallocated ether otherwise VM Exception: invalid JUMP function SikobaPresale () payable { // RECOMMENDATION: Move next line into the Owned constructor so Owned is self-contained owner = msg.sender; //TODO: check if having 100 participants in preallocation would not run the contract out of gas on mainnet // RECOMMENDATION: Move totalPreallocationInWei into a public constant //TODO: exchange the following example with the real preallocation: // Preallocation Ether must be sent on construction or contract creation fails // // Declare sum of preallocation balances // and verify that the actual amount sent corresponds uint totalPreallocationInWei = 15 ether; assertEquals(totalPreallocationInWei, msg.value); // CHECK: If you have too many preallocations the following section may blow the gas limit // RECOMMENDATION: Use as is unless there are too many preallocations, then rewrite to use // a fill(...) and seal() function only if required. This alternative will // add complexity which we want to minimise. Example in ExtraBalToken Contract // http://ethereum.stackexchange.com/questions/7265. If you do have to use // fill and seal, deploy the contract, fill and seal, and only after a // check do you send the preallocation ethers //TODO: exchange with real address and ether value // Pre-allocations (hard-coded based on ETH values previously received) addBalance(0xdeadbeef, 10 wei, true); addBalance(0xcafebabe, 5 wei, true); } /// @notice Send at least `MINIMAL_AMOUNT_TO_SEND` and at most `MAXIMAL_AMOUNT_TO_SEND` ether to this contract /// @notice or the transaction will fail and the value will be given back. /// Timeframe: if `now` >= `START_DATE_PRESALE` and `now` <= `DEADLINE_DATE_PRESALE` /// `MAXIMAL_BALANCE_OF_PRESALE-balance` still payable. // CHECK: Ok function () payable { //preconditions to be met: // only during the public presale period // CHECK: Investors cannot fund before presale start date if (now < START_DATE_PRESALE) throw; // CHECK: Investors cannot fund before and after the presale period if (now > DEADLINE_DATE_PRESALE) throw; // the value sent must be between the minimal and maximal amounts accepted // CHECK: Investors cannot send small amounts if (msg.value < MINIMAL_AMOUNT_TO_SEND) throw; // CHECK: Investors can only send ethers within a range if (msg.value > MAXIMAL_AMOUNT_TO_SEND) throw; // check if maximum presale amount has been met already // CHECK: Investors cannot send ethers if the maximum has been reached // RECOMMENDATION: As Roland pointed out, this.balance includes the ethers sent with the // current transaction, so this check does not work as intended. Roland has // fixed this logic if (safeIncrement(this.balance, msg.value) > MAXIMAL_BALANCE_OF_PRESALE) throw; // register payment // CHECK: Ok addBalance(msg.sender, msg.value, false); } /// @notice owner withdraws ether from presale /// @dev the owner can transfer ether anytime from this contract if the presale succeeded // // CHECK: Only the owner can run this function // ACTION: There is a situation where ethers can be locked up accidentally // The situation is - presale completes and balance exceeds the minimum balance // The owner withdraws part of the balance and the balance drops below the presale // minimum balance. The owner can then no longer withdraw the remaining ethers as // an exception will be thrown // -> Rewrite the logic so that the ethers cannot get trapped by accident // FORMAT: Remove first blank line for consistency function withdraw(uint value) external onlyowner payable { // ?? check if balance can be withdrawn, // ?? TODO: probably redundand to basic ethereum checks and can be removed after clarification // CHECK: Ok if (this.balance < value) throw; // withdrawal IF PRESALE SUCCEEDED // only after the public deadline has been reached // and minimal presale goal is reached // CHECK: Owner can only run this after the presale end date if (now <= DEADLINE_DATE_PRESALE) throw; // CHECK: Owner can only run this after the presale end date and the balance is greater than // the presale minimum balance // CHECK: See RECOMMENDATION above. Ethers may get trapped if (this.balance < MINIMAL_BALANCE_OF_PRESALE) throw; // withdraw the amount wanted // CHECK: Ok bool success = owner.send(value); // basic check if sending failed, not really needed but good custom. // CHECK: This is an good check generally, but will make no difference in this case. But this is // more important in the following function. if (!success) throw; } /// @notice If `MINIMAL_BALANCE_OF_PRESALE` > `balance` after `DEADLINE_DATE_PRESALE` then you can withdraw your balance here /// @dev the owner can transfer ether anytime from this contract if the presale failed // FORMAT: Remove first blank line for consistency function withdrawYourAssetsIfPresaleFailed(uint value) external { // ?? check if balance can be withdrawn, // ?? TODO: probably redundand to basic ethereum checks and can be removed after clarification // CHECK: Ok if (this.balance < value) throw; // withdrawal IF PRESALE FAILED // only after the public deadline has been reached // and minimal presale goal was not reached // CHECK: Investor can only run this after the presale end date if (now <= DEADLINE_DATE_PRESALE) throw; // CHECK: Investor can only run this after the presale end date and the balance is less than // the presale minimum balance // CHECK: There is no issue here with ethers getting trapped. The balance must be less than // the presale minimum balance, and the balance will be reducing each time an investor // withdraws if (this.balance >= MINIMAL_BALANCE_OF_PRESALE) throw; // sender must have sent the amount he wants to withdraw // CHECK: Investor can withdraw amounts less than their balance if (balanceOf[msg.sender] < value) throw; // decrement the balance of the sender by the claimed amount // CHECK: Investor's balance is decremented balanceOf[msg.sender] = safeDecrement(balanceOf[msg.sender], value); // withdraw the amount wanted // CHECK: Ok bool success= msg.sender.send(value); // basic check if sending failed, not really needed but good custom. // CHECK: This is an important check. If the sending fails due to running out of gas, // the user's balance has already been deducted so the user cannot successfully // withdraw their balance if (!success) throw; } /// @dev private function to increment balances // RECOMMENDATION: See recommendation above regarding renaming valueInWei to value // FORMAT: Space after private, remove blank lines at start and end of function - for consistency function addBalance(address participant, uint valueInWei, bool isPreallocation) private{ // add amount to the balance of the participant balanceOf[participant] = safeIncrement(balanceOf[participant], valueInWei); // log the participation to easily gather them for furter processing LogParticipation(participant, valueInWei, now, isPreallocation); } //////////////////////////////////////////////////////////////////////////// /// @dev Simple Assertion like in Unit Testing frameworks. // CHECK: Ok function assertEquals(uint expectedValue, uint actualValue) private constant { if (expectedValue != actualValue) throw; } // FORMAT: 4 space indentation /// @dev if an addition is used for incrementing a base value, in order to detect an overflow /// we can simply check if the result is smaller than the base value. /// TODO: double check edge cases // CHECK: Ok function safeIncrement(uint base, uint increment) private constant returns (uint) { uint result = base + increment; if (result < base) throw; return result; } // FORMAT: 4 space indentation /// @dev if a subtraction is used for decrementing a base value, in order to detect an underflow /// we can simply check if the result is bigger than the base value. /// TODO: double check edge cases // CHECK: Ok function safeDecrement(uint base, uint increment) private constant returns (uint) { uint result = base - increment; if (result > base) throw; return result; } } |
1.3 Migrations.sol
From https://github.com/sikoba/token-presale/blob/master/contracts/Migrations.sol:
migrations.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
pragma solidity ^0.4.4; // FORMAT: 4 spaces indentation for consistency // RECOMMENDATION: Use uint256 instead of uint contract Migrations { address public owner; uint public last_completed_migration; // RECOMMENDATION: Rename restricted() to onlyOwner() as this is more generally used, and use // the version that throws an exception modifier restricted() { if (msg.sender == owner) _; } // CHECK: Ok function Migrations() { owner = msg.sender; } // CHECK: Ok function setCompleted(uint completed) restricted { last_completed_migration = completed; } // CHECK: Ok function upgrade(address new_address) restricted { Migrations upgraded = Migrations(new_address); upgraded.setCompleted(last_completed_migration); } } |
2. The First Revision SikobaPresale.sol Contract
The latest version of SikobaPresale.sol was taken from https://github.com/sikoba/token-presale/commit/f58535440228e980ade19b0b44bfd4381b556cd1 that includes Roland’s addition of the totalFunding
changes to correct for the only serious issue in the first round of reviews.
The formatting and recommendation from the first round of reviews was then applied to the code from github. Migrations.sol is not required for production deployment and has been omitted.
Following is the resulting code:
The First Revision SikobaPresale.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
pragma solidity ^0.4.8; /** * SIKOBA PRESALE CONTRACTS * * Version 0.1 * * Author Roland Kofler, Alex Kampa * * MIT LICENSE Copyright 2016 Sikoba LTD * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. **/ contract Owned { address public owner; function Owned() { owner = msg.sender; } modifier onlyOwner() { if (msg.sender != owner) { throw; } _; } } /// ---------------------------------------------------------------------------------------- /// @title Sikoba Presale Contract /// @author Roland Kofler, Alex Kampa /// @dev Changes to this contract will invalidate any security audits done before. /// It is MANDATORY to protocol audits in the "Security reviews done" section /// # Security checklists to use in each review: /// - Consensys checklist https://github.com/ConsenSys/smart-contract-best-practices /// - Roland Kofler's checklist https://github.com/rolandkofler/ether-security /// - Read all of the code and use creative and lateral thinking to discover bugs /// # Security reviews done: /// Date Auditors Short summary of the review executed /// 2017 MAR 3 - Roland Kofler - NO SECURITY REVIEW DONE /// 2017 MAR 7 - Roland Kofler, - Informal Security Review; added overflow protections; /// Alex Kampa fixed wrong inequality operators; added maximum amount // per transactions /// 2017 MAR 7 - Alex Kampa - Some code clean up; removed restriction of /// MINIMUM_PARTICIPATION_AMOUNT for preallocations /// ---------------------------------------------------------------------------------------- contract SikobaPresale is Owned { // ------------------------------------------------------------------------------------- // TODO Before deployment of contract to Mainnet // 1. Confirm MINIMUM_PARTICIPATION_AMOUNT and MAXIMUM_PARTICIPATION_AMOUNT below // 2. Adjust PRESALE_MINIMUM_FUNDING and PRESALE_MAXIMUM_FUNDING to desired EUR // equivalents // 3. Adjust PRESALE_START_DATE and confirm the presale period // 4. Update TOTAL_PREALLOCATION_IN_WEI to the total preallocations received // 5. Add each preallocation address and funding amount from the Sikoba bookmaker // to the constructor function // 6. Test the deployment to a dev blockchain or Testnet to confirm the constructor // will not run out of gas as this will vary with the number of preallocation // account entries // 7. A stable version of Solidity has been used. Check for any major bugs in the // Solidity release announcements after this version. // 8. Remember to send the preallocated funds when deploying the contract! // ------------------------------------------------------------------------------------- // Keep track of the total funding amount uint256 public totalFunding; // Minimum and maximum amounts per transaction for public participants uint256 public constant MINIMUM_PARTICIPATION_AMOUNT = 5 ether; uint256 public constant MAXIMUM_PARTICIPATION_AMOUNT = 250 ether; // Minimum and maximum goals of the presale uint256 public constant PRESALE_MINIMUM_FUNDING = 9000 ether; uint256 public constant PRESALE_MAXIMUM_FUNDING = 18000 ether; // Total preallocation in wei uint256 public constant TOTAL_PREALLOCATION_IN_WEI = 15 ether; // Public presale period // Starts 04/05/2017 @ 12:00pm (UTC) 2017-04-05T12:00:00+00:00 in ISO 8601 // Ends 2 weeks after the start uint256 public constant PRESALE_START_DATE = 1491393600; uint256 public constant PRESALE_END_DATE = PRESALE_START_DATE + 2 weeks; /// @notice Keep track of all participants contributions, including both the /// preallocation and public phases /// @dev Name complies with ERC20 token standard, etherscan for example will recognize /// this and show the balances of the address mapping (address => uint256) public balanceOf; /// @notice Log an event for each funding contribution, including the preallocated funds /// and funds submitted during the public phase event LogParticipation(address indexed sender, uint256 value, uint256 timestamp, bool isPreallocation); function SikobaPresale () payable { assertEquals(TOTAL_PREALLOCATION_IN_WEI, msg.value); // Pre-allocations addBalance(0xdeadbeef, 10 wei, true); addBalance(0xcafebabe, 5 wei, true); // TODO: Check the following line assertEquals(TOTAL_PREALLOCATION_IN_WEI, totalFunding); } /// @notice A participant sends a contribution to the contract's address /// between the PRESALE_STATE_DATE and the PRESALE_END_DATE /// @notice Only contributions between the MINIMUM_PARTICIPATION_AMOUNT and /// MAXIMUM_PARTICIPATION_AMOUNT are accepted. Otherwise the transaction /// is rejected and contributed amount is returned to the participant's /// account /// @notice A participant's contribution will be rejected if the presale /// has been funded to the maximum amount function () payable { // A participant cannot send funds before the presale start date if (now < PRESALE_START_DATE) throw; // A participant cannot send funds after the presale end date if (now > PRESALE_END_DATE) throw; // A participant cannot send less than the minimum amount if (msg.value < MINIMUM_PARTICIPATION_AMOUNT) throw; // A participant cannot send more than the maximum amount if (msg.value > MAXIMUM_PARTICIPATION_AMOUNT) throw; // A participant cannot send funds if the presale has been reached the maximum // funding amount if (safeIncrement(totalFunding, msg.value) > PRESALE_MAXIMUM_FUNDING) throw; // Register the participant's contribution addBalance(msg.sender, msg.value, false); } /// @notice The owner can withdraw ethers after the presale has completed, /// only if the minimum funding amount has been reached function ownerWithdraw(uint256 value) external onlyOwner payable { // The owner cannot withdraw before the presale ends if (now <= PRESALE_END_DATE) throw; // The owner cannot withdraw if the presale did not reach the minimum funding amount if (totalFunding < PRESALE_MINIMUM_FUNDING) throw; // Withdraw the amount requested if (!owner.send(value)) throw; } /// @notice The participant will need to withdraw their funds from this contract if /// the presale has failed by not reaching the minimum funding amount function participantWithdrawIfPresaleFailed(uint256 value) external { // Participant cannot withdraw before the presale ends if (now <= PRESALE_END_DATE) throw; // Participant cannot withdraw if the minimum funding amount has been reached if (totalFunding >= PRESALE_MINIMUM_FUNDING) throw; // Participant can only withdraw an amount up to their contributed balance if (balanceOf[msg.sender] < value) throw; // Participant's balance is reduced by the claimed amount. balanceOf[msg.sender] = safeDecrement(balanceOf[msg.sender], value); // Send ethers back to the participant's account if (!msg.sender.send(value)) throw; } /// @dev Keep track of participants contributions and the total funding amount function addBalance(address participant, uint256 value, bool isPreallocation) private { // Participant's balance is increased by the sent amount balanceOf[participant] = safeIncrement(balanceOf[participant], value); // Keep track of the total funding amount totalFunding = safeIncrement(totalFunding, value); // Log an event of the participant's contribution LogParticipation(participant, value, now, isPreallocation); } /// @dev Throw an exception if the amounts are not equal function assertEquals(uint256 expectedValue, uint256 actualValue) private constant { if (expectedValue != actualValue) throw; } /// @dev Add a number to a base value. Detect overflows by checking the result is larger /// than the original base value. function safeIncrement(uint256 base, uint256 increment) private constant returns (uint256) { uint256 result = base + increment; if (result < base) throw; return result; } /// @dev Subtract a number from a base value. Detect underflows by checking that the result /// is smaller than the original base value function safeDecrement(uint256 base, uint256 increment) private constant returns (uint256) { uint256 result = base - increment; if (result > base) throw; return result; } } |
3. The Second Revision SikobaPresale.sol Contract
Roland has merged the results from the first revision of the reviews to the Github repository. This version is to be security reviewed in the second round.
The second revision of SikobaPresale.sol is based on the source code taken from https://github.com/sikoba/token-presale/blob/0d4f6f1b237853e1a6ff74de10e81f50eaf914a5/contracts/SikobaPresale.sol that includes the changes from the first revision.
The ad-hoc tests were conducted using Remix (Browser Solidity) using the JavaScript VM – remix-159aeff.zip
.
3.1 SikobaPresale.sol
Following is the second revision of SikobaPresale.sol with my additional comments marked as:
// CHECK:
for the check done// RECOMMENDATION:
for any recommendations// ACTION:
for areas that require action
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
pragma solidity ^0.4.8; /** * SIKOBA PRESALE CONTRACTS * * Version 0.1 * * Author Roland Kofler, Alex Kampa, Bok 'Bokky Poo Bah' Khoo * * MIT LICENSE Copyright 2016 Sikoba LTD * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. **/ // CHECK: Ok contract Owned { // CHECK: Ok address public owner; // CHECK: Ok - Confirmed that owner is set correctly when the SikobaPresale constructor is executed function Owned() { owner = msg.sender; } // CHECK: Ok - Confirmed that functions with this modifier can only be called by the owner modifier onlyOwner() { if (msg.sender != owner) { throw; } _; } } /// ---------------------------------------------------------------------------------------- /// @title Sikoba Presale Contract /// @author Roland Kofler, Alex Kampa, Bok 'Bokky Poo Bah' Khoo /// @dev Changes to this contract will invalidate any security audits done before. /// It is MANDATORY to protocol audits in the "Security reviews done" section /// # Security checklists to use in each review: /// - Consensys checklist https://github.com/ConsenSys/smart-contract-best-practices /// - Roland Kofler's checklist https://github.com/rolandkofler/ether-security /// - Read all of the code and use creative and lateral thinking to discover bugs /// # Security reviews done: /// Date Auditors Short summary of the review executed /// 2017 MAR 3 - Roland Kofler - NO SECURITY REVIEW DONE /// 2017 MAR 7 - Roland Kofler, - Informal Security Review; added overflow protections; /// Alex Kampa fixed wrong inequality operators; added maximum amount // per transactions /// 2017 MAR 7 - Alex Kampa - Some code clean up; removed restriction of /// MINIMUM_PARTICIPATION_AMOUNT for preallocations /// 2017 MAR 8 - Bok Khoo - Complete security review and modifications /// 2017 MAR 9 - Roland Kofler - Check the diffs between MAR 8 and MAR 7 versions /// ---------------------------------------------------------------------------------------- contract SikobaPresale is Owned { // ------------------------------------------------------------------------------------- // TODO Before deployment of contract to Mainnet // 1. Confirm MINIMUM_PARTICIPATION_AMOUNT and MAXIMUM_PARTICIPATION_AMOUNT below // 2. Adjust PRESALE_MINIMUM_FUNDING and PRESALE_MAXIMUM_FUNDING to desired EUR // equivalents // 3. Adjust PRESALE_START_DATE and confirm the presale period // 4. Update TOTAL_PREALLOCATION_IN_WEI to the total preallocations received // 5. Add each preallocation address and funding amount from the Sikoba bookmaker // to the constructor function // 6. Test the deployment to a dev blockchain or Testnet to confirm the constructor // will not run out of gas as this will vary with the number of preallocation // account entries // 7. A stable version of Solidity has been used. Check for any major bugs in the // Solidity release announcements after this version. // 8. Remember to send the preallocated funds when deploying the contract! // ------------------------------------------------------------------------------------- // CHECK: Ok. Starts off initialised with a 0 value when the contract is deployed // Keep track of the total funding amount uint256 public totalFunding; // CHECK: Ok // Minimum and maximum amounts per transaction for public participants uint256 public constant MINIMUM_PARTICIPATION_AMOUNT = 5 ether; uint256 public constant MAXIMUM_PARTICIPATION_AMOUNT = 250 ether; // CHECK: Ok // Minimum and maximum goals of the presale uint256 public constant PRESALE_MINIMUM_FUNDING = 9000 ether; uint256 public constant PRESALE_MAXIMUM_FUNDING = 18000 ether; // CHECK: Ok. Could leave off '_IN_WEI' to simplify, but not important // Total preallocation in wei uint256 public constant TOTAL_PREALLOCATION_IN_WEI = 15 ether; // CHECK: Ok // CHECK: PRESALE_START_DATE 1491393600 = 04/05/2017 @ 12:00pm (UTC) // http://www.unixtimestamp.com/index.php // CHECK: PRESALE_END_DATE calculated as 1492603200 = 04/19/2017 @ 12:00pm (UTC) // This is 14 days or 2 weeks from the PRESALE_START_DATE // Public presale period // Starts 04/05/2017 @ 12:00pm (UTC) 2017-04-05T12:00:00+00:00 in ISO 8601 // Ends 2 weeks after the start uint256 public constant PRESALE_START_DATE = 1491393600; uint256 public constant PRESALE_END_DATE = PRESALE_START_DATE + 2 weeks; // CHECK: Ok /// @notice Keep track of all participants contributions, including both the /// preallocation and public phases /// @dev Name complies with ERC20 token standard, etherscan for example will recognize /// this and show the balances of the address mapping (address => uint256) public balanceOf; // CHECK: Ok /// @notice Log an event for each funding contribution, including the preallocated funds /// and funds submitted during the public phase event LogParticipation(address indexed sender, uint256 value, uint256 timestamp, bool isPreallocation); // CHECK: 1. Only the owner will deploy this contract and therefore execute the constructor // CHECK: 2. Depending on the number of preallocation entries, the gas may exceed the current Ethereum // block gas limit. Roland has tested that 150 preallocations will fit within the 4 million // block gas limit. I just checked and 3 out of the last 4 blocks had 0 transactions, so // a contract creation requiring a lot of gas should spill over into the next block with // sufficient gas limit remaining. // If necessary, use the fill() and seal() method in the "ExtraBalToken Contract" in // http://ethereum.stackexchange.com/questions/7265/how-do-i-get-a-refund-for-the-amount-i-paid-in-excess-of-1-ether-to-100-the-dao // ACTION: 1. The owner will have to send the ethers raised in the preallocation phase. If the constructor // fails to execute correctly, the contract may receive the ethers and the ethers could get // trapped. Check the possibility of this occurring in more detail. function SikobaPresale () payable { assertEquals(TOTAL_PREALLOCATION_IN_WEI, msg.value); // Pre-allocations addBalance(0xdeadbeef, 10 wei, true); addBalance(0xcafebabe, 5 wei, true); // CHECK: Ok. Confirmed that the following assertion is a reasonable check if the sums don't add up // The following comment line can be deleted // TODO: Check the following line assertEquals(TOTAL_PREALLOCATION_IN_WEI, totalFunding); } // CHECK: Ok overall /// @notice A participant sends a contribution to the contract's address /// between the PRESALE_STATE_DATE and the PRESALE_END_DATE /// @notice Only contributions between the MINIMUM_PARTICIPATION_AMOUNT and /// MAXIMUM_PARTICIPATION_AMOUNT are accepted. Otherwise the transaction /// is rejected and contributed amount is returned to the participant's /// account /// @notice A participant's contribution will be rejected if the presale /// has been funded to the maximum amount function () payable { // CHECK: Ok. Confirmed by testing // A participant cannot send funds before the presale start date if (now < PRESALE_START_DATE) throw; // CHECK: Ok // A participant cannot send funds after the presale end date if (now > PRESALE_END_DATE) throw; // CHECK: Ok // A participant cannot send less than the minimum amount if (msg.value < MINIMUM_PARTICIPATION_AMOUNT) throw; // CHECK: Ok // A participant cannot send more than the maximum amount if (msg.value > MAXIMUM_PARTICIPATION_AMOUNT) throw; // CHECK: Ok // A participant cannot send funds if the presale has been reached the maximum // funding amount if (safeIncrement(totalFunding, msg.value) > PRESALE_MAXIMUM_FUNDING) throw; // CHECK: Ok // Register the participant's contribution addBalance(msg.sender, msg.value, false); } // CHECK: 1. Confirmed by testing that only the owner can execute this function // CHECK: 2. Ok overall /// @notice The owner can withdraw ethers after the presale has completed, /// only if the minimum funding amount has been reached function ownerWithdraw(uint256 value) external onlyOwner payable { // CHECK: Ok. Tested before PRESALE_START_DATE // The owner cannot withdraw before the presale ends if (now <= PRESALE_END_DATE) throw; // CHECK: Ok. The old logic issue using the potentially decreasing contract account // balance has been replaced with the totalFunding variable that will be // constant after the presale period // The owner cannot withdraw if the presale did not reach the minimum funding amount if (totalFunding < PRESALE_MINIMUM_FUNDING) throw; // CHECK: The send(...) function is potentially risky, but in this case the owner will be // calling this function // Withdraw the amount requested if (!owner.send(value)) throw; } /// @notice The participant will need to withdraw their funds from this contract if /// the presale has failed by not reaching the minimum funding amount function participantWithdrawIfPresaleFailed(uint256 value) external { // CHECK: Ok. Tested before PRESALE_START_DATE // Participant cannot withdraw before the presale ends if (now <= PRESALE_END_DATE) throw; // CHECK: Ok // Participant cannot withdraw if the minimum funding amount has been reached if (totalFunding >= PRESALE_MINIMUM_FUNDING) throw; // Participant can only withdraw an amount up to their contributed balance // CHECK: Ok if (balanceOf[msg.sender] < value) throw; // Participant's balance is reduced by the claimed amount. balanceOf[msg.sender] = safeDecrement(balanceOf[msg.sender], value); // CHECK: 1. The send() function only provides 2,300 gas when sending ethers to // the destination account. If the destination account is a contract, // the 2,300 gas is only sufficient for the fallback () function to // log an event. This limited gas amount will mitigate against a // reentrancy attack. // CHECK: 2. This send() function occurs at the end of this function. There is // no code to execute after this send() function, so there should be // no issue with the control flow being hijacked. // Send ethers back to the participant's account if (!msg.sender.send(value)) throw; } /// @dev Keep track of participants contributions and the total funding amount function addBalance(address participant, uint256 value, bool isPreallocation) private { // CHECK: Ok. Confirmed that the balance is set for the participant when called in the constructor // Participant's balance is increased by the sent amount balanceOf[participant] = safeIncrement(balanceOf[participant], value); // CHECK: Ok. Confirmed that totalSupply is incremented when called in the constructor // Keep track of the total funding amount totalFunding = safeIncrement(totalFunding, value); // ACTION: Note that the preallocated balances added in the constructor does not generate // an event log. As Roland mentioned, the isPreallocation flag is redundant as // the logs are not generated anyway. // Log an event of the participant's contribution LogParticipation(participant, value, now, isPreallocation); } // CHECK: Ok overall /// @dev Throw an exception if the amounts are not equal function assertEquals(uint256 expectedValue, uint256 actualValue) private constant { if (expectedValue != actualValue) throw; } // CHECK: Ok overall /// @dev Add a number to a base value. Detect overflows by checking the result is larger /// than the original base value. // CHECK: 1. "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" = // "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" // CHECK: 2. "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // "0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" = // VM Exception: invalid JUMP at ... function safeIncrement(uint256 base, uint256 increment) private constant returns (uint256) { uint256 result = base + increment; if (result < base) throw; return result; } // CHECK: Ok overall /// @dev Subtract a number from a base value. Detect underflows by checking that the result /// is smaller than the original base value // CHECK: 1. 10,10 = 0 // CHECK: 2. 10,11 = VM Exception: invalid JUMP at ... function safeDecrement(uint256 base, uint256 increment) private constant returns (uint256) { uint256 result = base - increment; if (result > base) throw; return result; } } |
3.2 Confirming Events Are Not Logged In The Constructor
Deploying the following script in Remix (Browser Solidity) shows that event logs are NOT generated or persisted during the execution of the constructor. Once constructed, the event logs are generated and persisted.
Test Confirming Events Are Not Logged In The Constructor
1 2 3 4 5 6 7 8 9 10 11 12 13 |
pragma solidity ^0.4.8; contract Test { event LogMessage(string message); function Test() { LogMessage("Not expecting this to be logged"); } function logMessage() { LogMessage("Expecting this to be logged"); } } |
The logging of the preallocation balances in the SikobaPresale constructor is redundant.
4. The Third And Final Revision SikobaPresale.sol Contract
Roland has merged the results from the second revision of the reviews to the Github repository. This version is to be security reviewed in the third round.
The third revision of SikobaPresale.sol is based on the source code taken from https://github.com/sikoba/token-presale/blob/f4cf1ab95e7b18cf0612076968342a4a4e1087f3/contracts/SikobaPresale.sol that includes the changes from the second revision and minor updates. Here are diff1, diff2 and diff3.
4.1 SikobaPresale.sol
Following is the third and final revision of SikobaPresale.sol.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
pragma solidity ^0.4.8; /** * SIKOBA PRESALE CONTRACTS * * Version 0.1 * * Author Roland Kofler, Alex Kampa, Bok 'BokkyPooBah' Khoo * * MIT LICENSE Copyright 2016 Sikoba LTD * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. **/ contract Owned { address public owner; function Owned() { owner = msg.sender; } modifier onlyOwner() { if (msg.sender != owner) { throw; } _; } } /// ---------------------------------------------------------------------------------------- /// @title Sikoba Presale Contract /// @author Roland Kofler, Alex Kampa, Bok 'Bokky Poo Bah' Khoo /// @dev Changes to this contract will invalidate any security audits done before. /// It is MANDATORY to protocol audits in the "Security reviews done" section /// # Security checklists to use in each review: /// - Consensys checklist https://github.com/ConsenSys/smart-contract-best-practices /// - Roland Kofler's checklist https://github.com/rolandkofler/ether-security /// - Read all of the code and use creative and lateral thinking to discover bugs /// # Security reviews done: /// Date Auditors Short summary of the review executed /// Mar 03 2017 - Roland Kofler - NO SECURITY REVIEW DONE /// Mar 07 2017 - Roland Kofler, - Informal Security Review; added overflow protections; /// Alex Kampa fixed wrong inequality operators; added maximum amount /// per transactions /// Mar 07 2017 - Alex Kampa - Some code clean up; removed restriction of /// MINIMUM_PARTICIPATION_AMOUNT for preallocations /// Mar 08 2017 - Bok Khoo - Complete security review and modifications /// Mar 09 2017 - Roland Kofler - Check the diffs between MAR 8 and MAR 7 versions /// Mar 12 2017 - Bok Khoo - Renamed TOTAL_PREALLOCATION_IN_WEI /// to TOTAL_PREALLOCATION. /// Removed isPreAllocation from addBalance(...) /// Mar 13 2017 - Bok Khoo - Made dates in comments consistent /// ---------------------------------------------------------------------------------------- contract SikobaPresale is Owned { // ------------------------------------------------------------------------------------- // TODO Before deployment of contract to Mainnet // 1. Confirm MINIMUM_PARTICIPATION_AMOUNT and MAXIMUM_PARTICIPATION_AMOUNT below // 2. Adjust PRESALE_MINIMUM_FUNDING and PRESALE_MAXIMUM_FUNDING to desired EUR // equivalents // 3. Adjust PRESALE_START_DATE and confirm the presale period // 4. Update TOTAL_PREALLOCATION to the total preallocations received // 5. Add each preallocation address and funding amount from the Sikoba bookmaker // to the constructor function // 6. Test the deployment to a dev blockchain or Testnet to confirm the constructor // will not run out of gas as this will vary with the number of preallocation // account entries // 7. A stable version of Solidity has been used. Check for any major bugs in the // Solidity release announcements after this version. // 8. Remember to send the preallocated funds when deploying the contract! // ------------------------------------------------------------------------------------- // Keep track of the total funding amount uint256 public totalFunding; // Minimum and maximum amounts per transaction for public participants uint256 public constant MINIMUM_PARTICIPATION_AMOUNT = 5 ether; uint256 public constant MAXIMUM_PARTICIPATION_AMOUNT = 250 ether; // Minimum and maximum goals of the presale uint256 public constant PRESALE_MINIMUM_FUNDING = 9000 ether; uint256 public constant PRESALE_MAXIMUM_FUNDING = 18000 ether; // Total preallocation in wei uint256 public constant TOTAL_PREALLOCATION = 15 ether; // Public presale period // Starts Apr 05 2017 @ 12:00pm (UTC) 2017-04-05T12:00:00+00:00 in ISO 8601 // Ends 2 weeks after the start uint256 public constant PRESALE_START_DATE = 1491393600; uint256 public constant PRESALE_END_DATE = PRESALE_START_DATE + 2 weeks; // Owner can clawback after a date in the future, so no ethers remain // trapped in the contract. This will only be relevant if the // minimum funding level is not reached // Jan 01 2018 @ 12:00pm (UTC) 2018-01-01T12:00:00+00:00 in ISO 8601 uint256 public constant OWNER_CLAWBACK_DATE = 1514808000; /// @notice Keep track of all participants contributions, including both the /// preallocation and public phases /// @dev Name complies with ERC20 token standard, etherscan for example will recognize /// this and show the balances of the address mapping (address => uint256) public balanceOf; /// @notice Log an event for each funding contributed during the public phase /// @notice Events are not logged when the constructor is being executed during /// deployment, so the preallocations will not be logged event LogParticipation(address indexed sender, uint256 value, uint256 timestamp); function SikobaPresale () payable { assertEquals(TOTAL_PREALLOCATION, msg.value); // Pre-allocations addBalance(0xdeadbeef, 10 wei); addBalance(0xcafebabe, 5 wei); assertEquals(TOTAL_PREALLOCATION, totalFunding); } /// @notice A participant sends a contribution to the contract's address /// between the PRESALE_STATE_DATE and the PRESALE_END_DATE /// @notice Only contributions between the MINIMUM_PARTICIPATION_AMOUNT and /// MAXIMUM_PARTICIPATION_AMOUNT are accepted. Otherwise the transaction /// is rejected and contributed amount is returned to the participant's /// account /// @notice A participant's contribution will be rejected if the presale /// has been funded to the maximum amount function () payable { // A participant cannot send funds before the presale start date if (now < PRESALE_START_DATE) throw; // A participant cannot send funds after the presale end date if (now > PRESALE_END_DATE) throw; // A participant cannot send less than the minimum amount if (msg.value < MINIMUM_PARTICIPATION_AMOUNT) throw; // A participant cannot send more than the maximum amount if (msg.value > MAXIMUM_PARTICIPATION_AMOUNT) throw; // A participant cannot send funds if the presale has been reached the maximum // funding amount if (safeIncrement(totalFunding, msg.value) > PRESALE_MAXIMUM_FUNDING) throw; // Register the participant's contribution addBalance(msg.sender, msg.value); } /// @notice The owner can withdraw ethers after the presale has completed, /// only if the minimum funding level has been reached function ownerWithdraw(uint256 value) external onlyOwner { // The owner cannot withdraw before the presale ends if (now <= PRESALE_END_DATE) throw; // The owner cannot withdraw if the presale did not reach the minimum funding amount if (totalFunding < PRESALE_MINIMUM_FUNDING) throw; // Withdraw the amount requested if (!owner.send(value)) throw; } /// @notice The participant will need to withdraw their funds from this contract if /// the presale has not achieved the minimum funding level function participantWithdrawIfMinimumFundingNotReached(uint256 value) external { // Participant cannot withdraw before the presale ends if (now <= PRESALE_END_DATE) throw; // Participant cannot withdraw if the minimum funding amount has been reached if (totalFunding >= PRESALE_MINIMUM_FUNDING) throw; // Participant can only withdraw an amount up to their contributed balance if (balanceOf[msg.sender] < value) throw; // Participant's balance is reduced by the claimed amount. balanceOf[msg.sender] = safeDecrement(balanceOf[msg.sender], value); // Send ethers back to the participant's account if (!msg.sender.send(value)) throw; } /// @notice The owner can clawback any ethers after a date in the future, so no /// ethers remain trapped in this contract. This will only be relevant /// if the minimum funding level is not reached function ownerClawback() external onlyOwner { // The owner cannot withdraw before the clawback date if (now < OWNER_CLAWBACK_DATE) throw; // Send remaining funds back to the owner if (!owner.send(this.balance)) throw; } /// @dev Keep track of participants contributions and the total funding amount function addBalance(address participant, uint256 value) private { // Participant's balance is increased by the sent amount balanceOf[participant] = safeIncrement(balanceOf[participant], value); // Keep track of the total funding amount totalFunding = safeIncrement(totalFunding, value); // Log an event of the participant's contribution LogParticipation(participant, value, now); } /// @dev Throw an exception if the amounts are not equal function assertEquals(uint256 expectedValue, uint256 actualValue) private constant { if (expectedValue != actualValue) throw; } /// @dev Add a number to a base value. Detect overflows by checking the result is larger /// than the original base value. function safeIncrement(uint256 base, uint256 increment) private constant returns (uint256) { uint256 result = base + increment; if (result < base) throw; return result; } /// @dev Subtract a number from a base value. Detect underflows by checking that the result /// is smaller than the original base value function safeDecrement(uint256 base, uint256 increment) private constant returns (uint256) { uint256 result = base - increment; if (result > base) throw; return result; } } |
4.2 Test Scenarios
Test & Expected Results | Passed? |
1. At deployment of contract, before presale period | PASS |
1.1 Preallocation balances incorrect – fail deployment | PASS |
1.2 Preallocation balances correct – deploy | PASS |
1.3 No one can contribute to the contract | PASS |
2. During the presale period | PASS |
2.1 Participant cannot contribute below minimum amount | PASS |
2.2 Participant cannot contribute above maximum amount | PASS |
2.3 Participant can contribute in correct range | PASS |
2.4 Participant cannot contribute once funding max reached | PASS |
2.5 Owner cannot withdraw | PASS |
2.6 Owner cannot clawback | PASS |
2.7 Participant cannot withdraw | PASS |
3. After presale period when funding goal not reached | PASS |
3.1 Owner cannot withdraw | PASS |
3.2 Owner cannot clawback | PASS |
3.3 Participants cannot contribute | PASS |
3.4 Participants can withdraw partial amounts | PASS |
3.5 Participants can withdraw full amount | PASS |
4. After presale period when funding goal reached | PASS |
4.1 Participant cannot contribute | PASS |
4.2 Participant cannot withdraw | PASS |
4.3 Owner cannot clawback | PASS |
4.4 Owner can withdraw partial amounts | PASS |
4.5 Owner can withdraw full amount | PASS |
5. After clawback period when funding goal not reached | PASS |
5.1 Owner cannot withdraw | PASS |
5.2 Participants cannot contribute | PASS |
5.3 Participants can withdraw partial amounts | PASS |
5.4 Participants can withdraw full amount | PASS |
5.5 Owner can clawback | PASS |
4.3 Test Results
The test inputs, outputs and results were run at 02:16 Mar 14 2017 AEST and are available at https://github.com/sikoba/token-presale/tree/bc5bf008dd2ea0507b9c33514b2e0215d6643c9c/test (but does not include Roland’s preallocations.txt and sikobapresale.js). The tests were run using geth 1.5.9-stable and solc 0.4.9+commit.364da425.Darwin.appleclang on an OS/X notebook.
To view in a wider window, click on the <-> icon on the top right of the window.
4.3.1 Test 1. At deployment of contract, before presale period
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100045.000000000000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 100000.000000000000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 100000.000000000000000000 Account #4 - Presale Participant #1 sikobaPresaleAddress=null gas=800000 gasUsed=800000 cost=0.016 block=11 txId=0x7e7a7401b2326a81040345cd98f17938a53fc3b86645f0bfc5a8cce0f9c34624 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100055.016000000000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 99999.984000000000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 100000.000000000000000000 Account #4 - Presale Participant #1 PASS Test 1.1 Preallocation balances incorrect – fail deployment sikobaPresaleAddress=0xe9ba90b474ef72aa062d89a319c3e15b31e8fe65 gas=800000 gasUsed=585090 cost=0.0117018 block=13 txId=0xcf960cd03b9d1be89ea5866cfb32de5a7d2908f551c943ae6ef20be659506882 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100065.027701800000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 98599.972298200000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 100000.000000000000000000 Account #4 - Presale Participant #1 6 0xe9ba90b474ef72aa062d89a319c3e15b31e8fe65 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 1.2 Preallocation balances correct – deploy sendContributionFailTxId gas=100000 gasUsed=100000 cost=0.002 block=15 txId=0x54ca7b089f0addb8c9f5b58e181bfcf20aab9c5514e0a9e29a15acf50193d7c3 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100075.029701800000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 98599.972298200000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99999.998000000000000000 Account #4 - Presale Participant #1 6 0xe9ba90b474ef72aa062d89a319c3e15b31e8fe65 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 1.3 No one can contribute to the contract |
4.3.2 Test 2. During the presale period
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100090.029701800000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 98599.972298200000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99999.998000000000000000 Account #4 - Presale Participant #1 sikobaPresaleAddress=0x30da52ef30bdaec61b43317cc045e4f267eaf779 gas=800000 gasUsed=585090 cost=0.0117018 block=20 txId=0x6dd969ab6d1b5899590e06e3b09394ce7b9af4805398d4d3cfd0cb776d74479c # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100100.041403600000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.960596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99999.998000000000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 2 Contract Setup sendContribution21TxId gas=100000 gasUsed=100000 cost=0.002 block=22 txId=0x0a4c97b49daadcc5e3289e5dabfd337bf54c9bdb6663edeecd09ee57944d0437 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100110.043403600000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.960596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99999.996000000000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 2.1 Participant cannot contribute below minimum amount sendContribution22TxId gas=100000 gasUsed=100000 cost=0.002 block=24 txId=0xffb295349bb408302ce98ca09d6e420e6b03d81c6aaba85c8b40cdbafc21126b # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100120.045403600000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.960596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99999.994000000000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 2.2 Participant cannot contribute above maximum amount sendContribution23TxId gas=100000 gasUsed=48586 cost=0.00097172 block=26 txId=0x6c0f07f720577c3734767f560c97a3d0a8b7c1794eb164971be3c4617678f201 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100130.046375320000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.960596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.993028280000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1500.000000000000000000 SikobaPresaleContract #1 PASS Test 2.3 Participant can contribute in correct range sendContribution24TxId gas=100000 gasUsed=100000 cost=0.002 block=28 txId=0x2c2c915e4ab64581d3a36196c960daae0d19fb196daa6a07846bafb666618342 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100140.048375320000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.960596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1500.000000000000000000 SikobaPresaleContract #1 PASS Test 2.4 Participant cannot contribute once funding max reached withdraw25TxId gas=100000 gasUsed=100000 cost=0.002 block=30 txId=0x8693f345aad8345b2ff093bcd189f252d0f57042d89c7d2042bb196f3c4c87ba # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100150.050375320000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.958596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1500.000000000000000000 SikobaPresaleContract #1 PASS Test 2.5 Owner cannot withdraw clawback26TxId gas=100000 gasUsed=100000 cost=0.002 block=32 txId=0x9ca839f4816e01a53f56f6d5bcf94e95ac8ad8a3ed25b85dc626e4294788f8f2 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100160.052375320000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.956596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100000.000000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1500.000000000000000000 SikobaPresaleContract #1 PASS Test 2.6 Owner cannot clawback preallocationParticipant1Account balance=1000 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100170.054375320000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.956596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 99999.998000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 6 0x30da52ef30bdaec61b43317cc045e4f267eaf779 1500.000000000000000000 SikobaPresaleContract #1 participantWithdraw27TxId gas=100000 gasUsed=100000 cost=0.002 block=34 txId=0xf9544eca9bd14813f3755989c2c182b8465d49552f8e7a0e1ea8a9b07f689d9b PASS Test 2.7 Participant cannot withdraw |
4.3.3 Test 3. After presale period when funding goal not reached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100195.054375320000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 97199.956596400000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 99999.998000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 sikobaPresaleAddress=0x9a538a4dbfc9fa678ec782bb23fcbab065adbc6e gas=800000 gasUsed=585090 cost=0.0117018 block=41 txId=0x8b186563f95fd2a9701f9ecd0f6d6990248b156aea7c64c136eae339a5393a67 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100205.066077120000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.944894600000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 99999.998000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 6 0x9a538a4dbfc9fa678ec782bb23fcbab065adbc6e 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 3 Contract Setup withdraw31TxId gas=100000 gasUsed=100000 cost=0.002 block=43 txId=0x3cbb00d8aad3547d7837d3ef9de563ef29edbc4c51d9fc5ee3f3e80b97f3d923 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100215.068077120000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.942894600000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 99999.998000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 6 0x9a538a4dbfc9fa678ec782bb23fcbab065adbc6e 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 3.1 Owner cannot withdraw clawback32TxId gas=100000 gasUsed=100000 cost=0.002 block=45 txId=0x89caa85d487b2745366f078c4d2de486a8493b32b746a5686ef02b5fd87ef1f8 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100225.070077120000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.940894600000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 99999.998000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.991028280000000000 Account #4 - Presale Participant #1 6 0x9a538a4dbfc9fa678ec782bb23fcbab065adbc6e 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 3.2 Owner cannot clawback sendContribution33TxId gas=100000 gasUsed=100000 cost=0.002 block=47 txId=0xded48885add3263b9e0572ab73a02893f761e064d3e29d526bf2e0ef67ed5926 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100235.072077120000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.940894600000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 99999.998000000000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.989028280000000000 Account #4 - Presale Participant #1 6 0x9a538a4dbfc9fa678ec782bb23fcbab065adbc6e 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 3.3 Participants cannot contribute preallocationParticipant1Account balance=1000 participantWithdrawPartial34TxId gas=100000 gasUsed=34469 cost=0.00068938 block=49 txId=0x849641fe8c649aa88a36903e8177b83f7301ad7a337eec3a2626d1f59c32bfb9 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100245.072766500000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.940894600000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100099.997310620000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.989028280000000000 Account #4 - Presale Participant #1 6 0x9a538a4dbfc9fa678ec782bb23fcbab065adbc6e 1300.000000000000000000 SikobaPresaleContract #1 PASS Test 3.4 Participants can withdraw partial amounts preallocationParticipant1Account balance=900 participantWithdrawFull35TxId gas=100000 gasUsed=19469 cost=0.00038938 block=51 txId=0xbe4651a4ff9d8307ca75185cd41300483496ed4ab16efc68c91eedfdc42d88dd # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100255.073155880000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.940894600000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.996921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.989028280000000000 Account #4 - Presale Participant #1 6 0x9a538a4dbfc9fa678ec782bb23fcbab065adbc6e 400.000000000000000000 SikobaPresaleContract #1 PASS Test 3.5 Participants can withdraw full amount |
4.3.4 Test 4. After presale period when funding goal reached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100260.073155880000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.940894600000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.996921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.989028280000000000 Account #4 - Presale Participant #1 sikobaPresaleAddress=0xbd7780c8773a6c99163ea9c1fa870450bc216aa6 gas=800000 gasUsed=585090 cost=0.0117018 block=54 txId=0x0131946abad7cdb58a56cc3c43af227035e1d1b395eb82475fecbd32f7b8605b # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100270.084857680000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.929192800000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.996921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.989028280000000000 Account #4 - Presale Participant #1 6 0xbd7780c8773a6c99163ea9c1fa870450bc216aa6 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 4 Contract Setup sendContribution41TxId gas=100000 gasUsed=100000 cost=0.002 block=56 txId=0x3112d02bfe5648c0702bccd66361f80c20330c2e52c19ac460124866695d342e # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100280.086857680000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.929192800000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.996921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 6 0xbd7780c8773a6c99163ea9c1fa870450bc216aa6 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 4.1 Participants cannot contribute preallocationParticipant1Account balance=1000 participantWithdraw42TxId gas=100000 gasUsed=100000 cost=0.002 block=58 txId=0x28ac8e8b414aeb3b5114db7a88b46fa1ed8a8a943aa331c6ade9200648bcbb52 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100290.088857680000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.929192800000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 6 0xbd7780c8773a6c99163ea9c1fa870450bc216aa6 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 4.2 Participant cannot withdraw clawback43TxId gas=100000 gasUsed=100000 cost=0.002 block=60 txId=0xb78eca1d1f326b43e1e4b831553861088f106c98f0ca00cbdb3df43cd074fb06 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100300.090857680000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.927192800000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 6 0xbd7780c8773a6c99163ea9c1fa870450bc216aa6 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 4.3 Owner cannot clawback sikobaPresaleAddress balance=1400 Test 4.4 Owner can withdraw partial amounts - withdrawing 100 ETH ownerWithdrawPartial44TxId gas=100000 gasUsed=29203 cost=0.00058406 block=62 txId=0x95ba040f6ea48274f249b3676bf71e480f08f19840ef34ad9c2db710116548c5 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100310.091441740000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94499.926608740000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 6 0xbd7780c8773a6c99163ea9c1fa870450bc216aa6 1300.000000000000000000 SikobaPresaleContract #1 PASS Test 4.4 Owner can withdraw partial amounts sikobaPresaleAddress balance=1300 Test 4.5 Owner can withdraw full amount - withdrawing remainder ownerWithdrawRemainder45TxId gas=100000 gasUsed=29203 cost=0.00058406 block=64 txId=0x4ad9160d65aea2aef202b3b93fbc9c12d7b76f8bbbc16d971234e5d45a3440ff # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100320.092025800000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.926024680000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 6 0xbd7780c8773a6c99163ea9c1fa870450bc216aa6 0.000000000000000000 SikobaPresaleContract #1 PASS Test 4.5 Owner can withdraw full amount |
4.3.5 Test 5. After clawback period when funding goal not reached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100340.092025800000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 95799.926024680000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 sikobaPresaleAddress=0x9e4a8ecde6d50985f50348e7cbc8a32dc9da7548 gas=800000 gasUsed=585090 cost=0.0117018 block=70 txId=0xbc237e3057d7191f04fddf5a0bb758cfc1fb43616351b715a741afc6f68ccb0b # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100350.103727600000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.914322880000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 6 0x9e4a8ecde6d50985f50348e7cbc8a32dc9da7548 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 3 Contract Setup withdraw51TxId gas=100000 gasUsed=100000 cost=0.002 block=72 txId=0x98331773ec9c6381384600eef1ae35c6e179a8ffbb8a89f7409f1562ea9dfc3f # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100360.105727600000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.912322880000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.987028280000000000 Account #4 - Presale Participant #1 6 0x9e4a8ecde6d50985f50348e7cbc8a32dc9da7548 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 5.1 Owner cannot withdraw sendContribution52TxId gas=100000 gasUsed=100000 cost=0.002 block=74 txId=0x699ad46a24c96d8a0b92ec3050d42cd7a1084d871041f49045fe5e88b5d3c2e1 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100370.107727600000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.912322880000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 100999.994921240000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.985028280000000000 Account #4 - Presale Participant #1 6 0x9e4a8ecde6d50985f50348e7cbc8a32dc9da7548 1400.000000000000000000 SikobaPresaleContract #1 PASS Test 5.2 Participants cannot contribute preallocationParticipant1Account balance=1000 participantWithdrawPartial53TxId gas=100000 gasUsed=34469 cost=0.00068938 block=76 txId=0xaa6cfa14ae4b17ea99cb187d2394d0db6ead563e35c9436d9b9fba5451d8d013 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100380.108416980000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.912322880000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 101099.994231860000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.985028280000000000 Account #4 - Presale Participant #1 6 0x9e4a8ecde6d50985f50348e7cbc8a32dc9da7548 1300.000000000000000000 SikobaPresaleContract #1 PASS Test 5.3 Participants can withdraw partial amounts preallocationParticipant1Account balance=900 participantWithdrawFull54TxId gas=100000 gasUsed=19469 cost=0.00038938 block=78 txId=0x3e03038c9a5ce2625642226c0ab24ad201e422adac042122fc909b71bd7b4363 # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100390.108806360000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94399.912322880000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 101999.993842480000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.985028280000000000 Account #4 - Presale Participant #1 6 0x9e4a8ecde6d50985f50348e7cbc8a32dc9da7548 400.000000000000000000 SikobaPresaleContract #1 PASS Test 5.4 Participants can withdraw full amount clawback55TxId gas=100000 gasUsed=28734 cost=0.00057468 block=80 txId=0x093c41f2d66cf4d4f68400656de9fc4e4fb1bcf3166d268b80a43f894dee5e6a # Account EtherBalance Name 1 0x000d1009bd8f0b1301cc5edc28ed1222a3ce671e 100400.109381040000000000 Account #0 - Miner 2 0x0014060ff383c9b21c6840a3b14aab06741e5c49 94799.911748200000000000 Account #1 - Owner 3 0x0020017ba4c67f76c76b1af8c41821ee54f37171 101999.993842480000000000 Account #2 - Preallocation Participant #1 4 0x0036f6addb6d64684390f55a92f0f4988266901b 100000.000000000000000000 Account #3 - Preallocation Participant #2 5 0x004e64833635cd1056b948b57286b7c91e62731c 99899.985028280000000000 Account #4 - Presale Participant #1 6 0x9e4a8ecde6d50985f50348e7cbc8a32dc9da7548 0.000000000000000000 SikobaPresaleContract #1 PASS Test 5.5 Owner can clawback |
5. Testing Multisig Wallet Contributions To The SikobaPresale Contract
5.1 Deploying SikobaPresale.sol
The SikobaPresale.sol from section 4. above was deployed to Mainnet with the following parameters:
MINIMUM_PARTICIPATION_AMOUNT = 0 ether
MAXIMUM_PARTICIPATION_AMOUNT = 250 ether
PRESALE_MINIMUM_FUNDING = 1 ether
PRESALE_MAXIMUM_FUNDING = 2 ether
TOTAL_PREALLOCATION = 0 ether
PRESALE_START_DATE = now
(1490191425 = 2017-03-22T14:03:45+00:00)PRESALE_END_DATE = PRESALE_START_DATE + 15 minutes
(1490192325 = 2017-03-22T14:18:45+00:00)OWNER_CLAWBACK_DATE = PRESALE_START_DATE + 20 minutes
(1490192625 = 2017-03-22T14:23:45+00:00)- No preallocations in the constructor
Reference: How Does The Ethereum Multisig Contract Wallet Execute Contract Functions?
5.2 Test Scenarios
- Deploy the contract. Presale ends in 15 minutes. Clawback active in 20 minutes
- Contribute 0.01 ETH from a multisig wallet before presale ends
- Withdraw the contributions of 0.01 ETH using a multisig wallet after the presale ends
5.3 Test Results
- PASS Deployed the contract to 0xe67907329dafd1ff826523e3f491bec8733f7376. Presale ends in 15 minutes. Clawback active in 20 minutes.
- PASS Contributed 0.01 ETH from a multisig wallet before presale ends. Tx 0xded8a025… with 99,252 gas used:
PASS Balance of 0.01 ETH in the SikobaPresale contract:
- UNABLE TO EXECUTE – see below. Withdraw the contributions of 0.01 ETH using a multisig wallet after the presale ends
Conclusion: The multisig wallet created by Ethereum Wallet 0.8.9 at , originally deployed Aug-31-2016 07:12:59 PM +UTC interacts with the SikobaPresale contract without any issues in this test.
5.4 DEPLOYMENT WARNING
I deployed the SikobaPresale contract to 0xe67907329dafd1ff826523e3f491bec8733f7376 with the following datetime parameters:
1 2 3 4 5 6 7 8 |
uint256 public constant PRESALE_START_DATE = now; uint256 public constant PRESALE_END_DATE = PRESALE_START_DATE + 15 minutes; // Owner can clawback after a date in the future, so no ethers remain // trapped in the contract. This will only be relevant if the // minimum funding level is not reached // Jan 01 2018 @ 12:00pm (UTC) 2018-01-01T12:00:00+00:00 in ISO 8601 uint256 public constant OWNER_CLAWBACK_DATE = PRESALE_START_DATE + 20 minutes; |
I was expecting PRESALE_START_DATE = now
to be evaluated to a constant (the deployment time), PRESALE_END_DATE
and OWNER_CLAWBACK_DATE
to both be constants of 15 minutes and 20 minutes relative to the deployment time. But NO. These variables are NOT evaluated to constants. You can verify for yourself by viewing the contract’s READ CONTRACT page and refreshing the page periodically. These values for these three variables will change with the page refresh.
Here’s the page at 1490193613 = 2017-03-22T14:40:13+00:00:
And here’s the page again at 1490193727 = 2017-03-22T14:42:07+00:00. Note that the highlighted variables have changed:
WARNING. Do not use now
in the uint256 public constant
variables!
Further information: