├── .gitignore ├── LICENSE ├── README.md ├── abiToClip ├── binToClip ├── make └── src ├── AuthenticationManager.sol ├── DividendManager.sol ├── IcoPhaseManagement.sol ├── SafeMath.sol ├── SmartInvestmentFundToken-GasHog.sol ├── SmartInvestmentFundToken-v2.sol ├── SmartInvestmentFundToken.sol ├── TokenHolderSnapshotter.sol ├── TransparencyRelayer.sol ├── VoteSvp001.sol ├── VoteSvp002.sol └── VotingBase.sol /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | bin/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, Guy, Robot 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Investment Fund Token (SIFT) 2 | SIFT is an investment fund that uses volume spread analysis within cryptocurrency markets to increase in value. It comes off the back of years of experience of the Smart Trader software that has traditionally been used for forex and commodity trading. 3 | 4 | The smart contracts that back SIFT all expose different features and functionality. Whilst there is certain functionality that is deployed at the start of SIFT's lifecycle the intention is that we will add additional contracts to extend functionality moving forwards. 5 | 6 | # Contracts 7 | Five contracts are currently used to form SIFT. It is planned to add additional future contracts, in particular with regards to voting. These will integrate via the authentication manager and SIFT contracts. 8 | 9 | All contracts return a contract version in the format ###YYYYMMDDHHmmSS where ### is a unique number for a particular contract. This allows other contracts and calling applications to ensure the address specified is correct and that the calling code will support execution. 10 | 11 | The contracts use some basic code protections that are commonplace including SafeMath and checking for payload underflow which has been used to hijack balances from exchanges. 12 | 13 | Multiple contracts have been used, rather than a monolith, so that everything is easier to test and so that if any individual piece of the puzzle becomes broken it will be easier to work around or redeploy individual contracts at a future date (depending on the specific issue). We feel this improves quality and flexibility. 14 | 15 | ## Authentication Manager 16 | The authentication manager is responsible for manager user and contract rights to all other contracts. Two classes of authentication exist - admin users and account readers. 17 | 18 | Admin users get some special priviledges depending on the contract - this could be ending the ICO and withdrawing funds, for example. By default the creating user is assigned admin rights. 19 | 20 | Account readers are a special type of contract that get slightly higher rights on the main SIFT contract. They get a full list of all current account holders without needing to go through the blockchain to work it out. This allows things like paying dividends or voting to be a lot easier. 21 | 22 | In addition to add and remove methods to allow admins or account readers to be updated this contract provides the ability to check whether an address has had rights at any point in the past. There are methods provided for this (isCurrentOrPastAdmin / isCurrentOrPastAccountReader) as well as events that are fired whenever authentication rights are changed. 23 | 24 | ## ICO Phase Management 25 | 26 | The ICO Phase Manage contract is the main point of interaction during the ICO. People send money here and this contract has a special priviledge to mint coins. It only allows coins to be created during the timeframe specified for the ICO (Aug 1st for 45 days). 27 | 28 | Once the ICO phase finishes (September 15th 2017 00:00 GMT) the ICO can either be closed or abandoned. Closing an ICO marks to other contracts that we're in the trading phase for the fund and withdraws all the deposited ether to the admin. Abandoning the ICO results in all the funds being made available to the original investor. They can access their funds by calling the abandonedFundWithdrawal() method. 29 | 30 | ## Smart Investment Fund Token (SIFT) 31 | 32 | This is the main ERC-20 compliant tokenf or SIFT. It is a standard ERC-20 compliant token with a couple of add-ons. 33 | 34 | The first difference over a standard ERC-20 token is that SIFT is aware of the ICO Phase Management contract. It uses this to ensure SIFTs can only be sent when the ICO is over and closed successfully as well as allowing the ICO contract access to a mintTokens() method that only it can call and only during the ICO phase. 35 | 36 | The second difference is that an array in addition to a map is created of all account holders. This is used so that other contracts with account reader priviledges (such as Dividend Manager and ICO Phase Management) have full access to holder information in an easy way. 37 | 38 | ## Transparency Relayer 39 | 40 | The Transparency Relayer contract is a mostly standalone contract only integrating with the Authentication Manager to check for admin access. It is responsible for publishing fund values and account balance information that are received from SIFT's backend systems. This can be used for people to validate the values themselves (where API codes are publicised) as well as for an auditor to confirm at a future date that the values we published at each time were correct. 41 | 42 | The contract fires events when either the fund value or account balance are updated and the corresponding fundValues and accountBalances arrays are updated along with their Count() methods to return the length of the arrays for easier enumeration. 43 | 44 | Only admins may call publish methods which add new transparency data to the contract. 45 | 46 | ## Dividend Manager 47 | 48 | Dividends are paid by the dividend manager. Anybody can send ether to this contract. The ether is then divided by the total amount of shares and each shareholder then has a balance of ether set for them based on the number of shares they hold. If any rounding results in change this is sent back to the caller. 49 | 50 | Each address with a balance can call the withdrawDividend() method to release ether back to their account. Even if a shareholder sells their SIFT the balance will remain here for them to claim and never expires. 51 | 52 | This contract uses a request-withdrawal mechanism to get around any potential issues with the contract actively sending ether to shareholders - such as a single shareholder being set as a malevolant contract that causes the entire transaction to be thrown. 53 | 54 | # Building 55 | 56 | We build from source on Linux using solc, The version 1.0.0 release used solc 0.4.11+commit.68ef5810.Linux.g++. 57 | 58 | To make it easier to build a simple shell script called "make" is available in the source directory. Just run it with ./make (make sure it is chmod 755 first) and then solc will run against all contracts and the output ABI and bin files will be stored in the out folder. 59 | 60 | # Deployment 61 | 62 | Once built we deploy the contracts in a specific order: 63 | 64 | 1. Authentication Manager 65 | 2. ICO Phase Management (passing in authentication manager address in constructor) 66 | 3. Smart Investment Fund Token (passing in ICO phase management and authentication manager addresses in constructor) 67 | 4. Dividend Manager (passing in SIFT contract address in constructor) 68 | 5. Transparency Relayer (passing in authentication manager address in constructor) 69 | 70 | We then perform a couple more post-deploy steps: 71 | 72 | 6. Add ICO Phase Management as an account reader in Authentication Manager 73 | 7. Add Dividend Manager as an account reader in Authentication Manager 74 | 8. Set the SIFT contract address in the ICO Phase Management contract 75 | 76 | To help with deployment we have a couple of handy command-line scripts that can be used in an X environment (i.e. a Linux desktop). 77 | 78 | To copy a specific contract's ABI to the clipboard: 79 | ```bash 80 | ./abiToClip 81 | ``` 82 | 83 | For example to copy the Authentication Manager's ABI to the clipboard: 84 | ```bash 85 | ./abiToClip AuthenticationManager 86 | ``` 87 | 88 | You can also copy the binary output for a contract to the clipboard: 89 | ```bash 90 | ./binToClip 91 | ``` 92 | 93 | We use this once we've built the contracts to copy the binary code that we'll be deploying with if we're performing a manual or test deployment. 94 | 95 | 96 | # Validation 97 | 98 | All our contracts are publicly deployed and their source can be verified by etherscan. Full details of the deployed contracts can be found below. 99 | 100 | 101 | | Contract | Address | Source | Validate | 102 | |:---------|:--------|:-------|:---------| 103 | | Authentication Manager | 0xc6a3746aa3fec176559f0865fd5240159402a81f | https://git.io/vQNpq | https://etherscan.io/address/0xc6a3746aa3fec176559f0865fd5240159402a81f#code | 104 | | ICO Phase Management | 0xf8Fc0cc97d01A47E0Ba66B167B120A8A0DeAb949 | https://git.io/vQNpy | https://etherscan.io/address/0xf8Fc0cc97d01A47E0Ba66B167B120A8A0DeAb949#code | 105 | | Smart Investment Fund Token | 0x8a187d5285d316bcbc9adafc08b51d70a0d8e000 | https://git.io/vQNpH | https://etherscan.io/address/0x8a187d5285d316bcbc9adafc08b51d70a0d8e000#code | 106 | | Dividend Manager | 0x9599954b6ade1f00f36a95cdf3a1b773ba3be19a | https://git.io/vQNxp | https://etherscan.io/address/0x9599954b6ade1f00f36a95cdf3a1b773ba3be19a#code | 107 | | Transparency Relayer | 0x27c8566bfb73280606e58f60cb3374788a43d850 | https://git.io/vQNpx | https://etherscan.io/address/0x27c8566bfb73280606e58f60cb3374788a43d850#code | 108 | 109 | All contracts are built against SafeMath which can be found at https://git.io/vQNpw and the code for which is included in the validate links above. 110 | -------------------------------------------------------------------------------- /abiToClip: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat out/$1.abi |xclip -selection clipboard 3 | -------------------------------------------------------------------------------- /binToClip: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat out/$1.bin |xclip -selection clipboard 3 | -------------------------------------------------------------------------------- /make: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd src 3 | #solc --abi --bin --optimize -o ../out --overwrite SmartInvestmentFundToken.sol DividendManager.sol TransparencyRelayer.sol 4 | ../bin/solc --abi --bin --optimize -o ../out --overwrite SmartInvestmentFundToken-v2.sol 5 | cd .. 6 | rm out/SafeMath* 2>/dev/null -------------------------------------------------------------------------------- /src/AuthenticationManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | /* The authentication manager details user accounts that have access to certain priviledges and keeps a permanent ledger of who has and has had these rights. */ 4 | contract AuthenticationManager { 5 | /* Map addresses to admins */ 6 | mapping (address => bool) adminAddresses; 7 | 8 | /* Map addresses to account readers */ 9 | mapping (address => bool) accountReaderAddresses; 10 | 11 | /* Details of all admins that have ever existed */ 12 | address[] adminAudit; 13 | 14 | /* Details of all account readers that have ever existed */ 15 | address[] accountReaderAudit; 16 | 17 | /* Fired whenever an admin is added to the contract. */ 18 | event AdminAdded(address addedBy, address admin); 19 | 20 | /* Fired whenever an admin is removed from the contract. */ 21 | event AdminRemoved(address removedBy, address admin); 22 | 23 | /* Fired whenever an account-reader contract is added. */ 24 | event AccountReaderAdded(address addedBy, address account); 25 | 26 | /* Fired whenever an account-reader contract is removed. */ 27 | event AccountReaderRemoved(address removedBy, address account); 28 | 29 | /* When this contract is first setup we use the creator as the first admin */ 30 | function AuthenticationManager() { 31 | /* Set the first admin to be the person creating the contract */ 32 | adminAddresses[msg.sender] = true; 33 | AdminAdded(0, msg.sender); 34 | adminAudit.length++; 35 | adminAudit[adminAudit.length - 1] = msg.sender; 36 | } 37 | 38 | /* Gets the contract version for validation */ 39 | function contractVersion() constant returns(uint256) { 40 | // Admin contract identifies as 100YYYYMMDDHHMM 41 | return 100201707171503; 42 | } 43 | 44 | /* Gets whether or not the specified address is currently an admin */ 45 | function isCurrentAdmin(address _address) constant returns (bool) { 46 | return adminAddresses[_address]; 47 | } 48 | 49 | /* Gets whether or not the specified address has ever been an admin */ 50 | function isCurrentOrPastAdmin(address _address) constant returns (bool) { 51 | for (uint256 i = 0; i < adminAudit.length; i++) 52 | if (adminAudit[i] == _address) 53 | return true; 54 | return false; 55 | } 56 | 57 | /* Gets whether or not the specified address is currently an account reader */ 58 | function isCurrentAccountReader(address _address) constant returns (bool) { 59 | return accountReaderAddresses[_address]; 60 | } 61 | 62 | /* Gets whether or not the specified address has ever been an admin */ 63 | function isCurrentOrPastAccountReader(address _address) constant returns (bool) { 64 | for (uint256 i = 0; i < accountReaderAudit.length; i++) 65 | if (accountReaderAudit[i] == _address) 66 | return true; 67 | return false; 68 | } 69 | 70 | /* Adds a user to our list of admins */ 71 | function addAdmin(address _address) { 72 | /* Ensure we're an admin */ 73 | if (!isCurrentAdmin(msg.sender)) 74 | throw; 75 | 76 | // Fail if this account is already admin 77 | if (adminAddresses[_address]) 78 | throw; 79 | 80 | // Add the user 81 | adminAddresses[_address] = true; 82 | AdminAdded(msg.sender, _address); 83 | adminAudit.length++; 84 | adminAudit[adminAudit.length - 1] = _address; 85 | } 86 | 87 | /* Removes a user from our list of admins but keeps them in the history audit */ 88 | function removeAdmin(address _address) { 89 | /* Ensure we're an admin */ 90 | if (!isCurrentAdmin(msg.sender)) 91 | throw; 92 | 93 | /* Don't allow removal of self */ 94 | if (_address == msg.sender) 95 | throw; 96 | 97 | // Fail if this account is already non-admin 98 | if (!adminAddresses[_address]) 99 | throw; 100 | 101 | /* Remove this admin user */ 102 | adminAddresses[_address] = false; 103 | AdminRemoved(msg.sender, _address); 104 | } 105 | 106 | /* Adds a user/contract to our list of account readers */ 107 | function addAccountReader(address _address) { 108 | /* Ensure we're an admin */ 109 | if (!isCurrentAdmin(msg.sender)) 110 | throw; 111 | 112 | // Fail if this account is already in the list 113 | if (accountReaderAddresses[_address]) 114 | throw; 115 | 116 | // Add the user 117 | accountReaderAddresses[_address] = true; 118 | AccountReaderAdded(msg.sender, _address); 119 | accountReaderAudit.length++; 120 | accountReaderAudit[adminAudit.length - 1] = _address; 121 | } 122 | 123 | /* Removes a user/contracts from our list of account readers but keeps them in the history audit */ 124 | function removeAccountReader(address _address) { 125 | /* Ensure we're an admin */ 126 | if (!isCurrentAdmin(msg.sender)) 127 | throw; 128 | 129 | // Fail if this account is already not in the list 130 | if (!accountReaderAddresses[_address]) 131 | throw; 132 | 133 | /* Remove this admin user */ 134 | accountReaderAddresses[_address] = false; 135 | AccountReaderRemoved(msg.sender, _address); 136 | } 137 | } -------------------------------------------------------------------------------- /src/DividendManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "SmartInvestmentFundToken.sol"; 3 | import "SafeMath.sol"; 4 | 5 | contract DividendManager { 6 | using SafeMath for uint256; 7 | 8 | /* Our handle to the SIFT contract. */ 9 | SmartInvestmentFundToken siftContract; 10 | 11 | /* Handle payments we couldn't make. */ 12 | mapping (address => uint256) public dividends; 13 | 14 | /* Indicates a payment is now available to a shareholder */ 15 | event PaymentAvailable(address addr, uint256 amount); 16 | 17 | /* Indicates a dividend payment was made. */ 18 | event DividendPayment(uint256 paymentPerShare, uint256 timestamp); 19 | 20 | /* Create our contract with references to other contracts as required. */ 21 | function DividendManager(address _siftContractAddress) { 22 | /* Setup access to our other contracts and validate their versions */ 23 | siftContract = SmartInvestmentFundToken(_siftContractAddress); 24 | if (siftContract.contractVersion() != 500201707171440) 25 | throw; 26 | } 27 | 28 | /* Gets the contract version for validation */ 29 | function contractVersion() constant returns(uint256) { 30 | /* Dividend contract identifies as 600YYYYMMDDHHMM */ 31 | return 600201707171440; 32 | } 33 | 34 | /* Makes a dividend payment - we make it available to all senders then send the change back to the caller. We don't actually send the payments to everyone to reduce gas cost and also to 35 | prevent potentially getting into a situation where we have recipients throwing causing dividend failures and having to consolidate their dividends in a separate process. */ 36 | function () payable { 37 | if (siftContract.isClosed()) 38 | throw; 39 | 40 | /* Determine how much to pay each shareholder. */ 41 | uint256 validSupply = siftContract.totalSupply(); 42 | uint256 paymentPerShare = msg.value / validSupply; 43 | if (paymentPerShare == 0) 44 | throw; 45 | 46 | /* Enum all accounts and send them payment */ 47 | uint256 totalPaidOut = 0; 48 | for (uint256 i = 0; i < siftContract.tokenHolderCount(); i++) { 49 | address addr = siftContract.tokenHolder(i); 50 | uint256 dividend = paymentPerShare * siftContract.balanceOf(addr); 51 | dividends[addr] = dividends[addr].add(dividend); 52 | PaymentAvailable(addr, dividend); 53 | totalPaidOut = totalPaidOut.add(dividend); 54 | } 55 | 56 | // Attempt to send change 57 | uint256 remainder = msg.value.sub(totalPaidOut); 58 | if (remainder > 0 && !msg.sender.send(remainder)) { 59 | dividends[msg.sender] = dividends[msg.sender].add(remainder); 60 | PaymentAvailable(msg.sender, remainder); 61 | } 62 | 63 | /* Audit this */ 64 | DividendPayment(paymentPerShare, now); 65 | } 66 | 67 | /* Allows a user to request a withdrawal of their dividend in full. */ 68 | function withdrawDividend() { 69 | // Ensure we have dividends available 70 | if (dividends[msg.sender] == 0) 71 | throw; 72 | 73 | // Determine how much we're sending and reset the count 74 | uint256 dividend = dividends[msg.sender]; 75 | dividends[msg.sender] = 0; 76 | 77 | // Attempt to withdraw 78 | if (!msg.sender.send(dividend)) 79 | throw; 80 | } 81 | } -------------------------------------------------------------------------------- /src/IcoPhaseManagement.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "AuthenticationManager.sol"; 3 | import "SmartInvestmentFundToken.sol"; 4 | import "SafeMath.sol"; 5 | 6 | contract IcoPhaseManagement { 7 | using SafeMath for uint256; 8 | 9 | /* Defines whether or not we are in the ICO phase */ 10 | bool public icoPhase = true; 11 | 12 | /* Defines whether or not the ICO has been abandoned */ 13 | bool public icoAbandoned = false; 14 | 15 | /* Defines whether or not the SIFT contract address has yet been set. */ 16 | bool siftContractDefined = false; 17 | 18 | /* Defines the sale price during ICO */ 19 | uint256 constant icoUnitPrice = 10 finney; 20 | 21 | /* If an ICO is abandoned and some withdrawals fail then this map allows people to request withdrawal of locked-in ether. */ 22 | mapping(address => uint256) public abandonedIcoBalances; 23 | 24 | /* Defines our interface to the SIFT contract. */ 25 | SmartInvestmentFundToken smartInvestmentFundToken; 26 | 27 | /* Defines the admin contract we interface with for credentails. */ 28 | AuthenticationManager authenticationManager; 29 | 30 | /* Defines the time that the ICO starts. */ 31 | uint256 constant public icoStartTime = 1501545600; // August 1st 2017 at 00:00:00 UTC 32 | 33 | /* Defines the time that the ICO ends. */ 34 | uint256 constant public icoEndTime = 1505433600; // September 15th 2017 at 00:00:00 UTC 35 | 36 | /* Defines our event fired when the ICO is closed */ 37 | event IcoClosed(); 38 | 39 | /* Defines our event fired if the ICO is abandoned */ 40 | event IcoAbandoned(string details); 41 | 42 | /* Ensures that once the ICO is over this contract cannot be used until the point it is destructed. */ 43 | modifier onlyDuringIco { 44 | bool contractValid = siftContractDefined && !smartInvestmentFundToken.isClosed(); 45 | if (!contractValid || (!icoPhase && !icoAbandoned)) throw; 46 | _; 47 | } 48 | 49 | /* This modifier allows a method to only be called by current admins */ 50 | modifier adminOnly { 51 | if (!authenticationManager.isCurrentAdmin(msg.sender)) throw; 52 | _; 53 | } 54 | 55 | /* Create the ICO phase managerment and define the address of the main SIFT contract. */ 56 | function IcoPhaseManagement(address _authenticationManagerAddress) { 57 | /* A basic sanity check */ 58 | if (icoStartTime >= icoEndTime) 59 | throw; 60 | 61 | /* Setup access to our other contracts and validate their versions */ 62 | authenticationManager = AuthenticationManager(_authenticationManagerAddress); 63 | if (authenticationManager.contractVersion() != 100201707171503) 64 | throw; 65 | } 66 | 67 | /* Set the SIFT contract address as a one-time operation. This happens after all the contracts are created and no 68 | other functionality can be used until this is set. */ 69 | function setSiftContractAddress(address _siftContractAddress) adminOnly { 70 | /* This can only happen once in the lifetime of this contract */ 71 | if (siftContractDefined) 72 | throw; 73 | 74 | /* Setup access to our other contracts and validate their versions */ 75 | smartInvestmentFundToken = SmartInvestmentFundToken(_siftContractAddress); 76 | if (smartInvestmentFundToken.contractVersion() != 500201707171440) 77 | throw; 78 | siftContractDefined = true; 79 | } 80 | 81 | /* Gets the contract version for validation */ 82 | function contractVersion() constant returns(uint256) { 83 | /* ICO contract identifies as 300YYYYMMDDHHMM */ 84 | return 300201707171440; 85 | } 86 | 87 | /* Close the ICO phase and transition to execution phase */ 88 | function close() adminOnly onlyDuringIco { 89 | // Forbid closing contract before the end of ICO 90 | if (now <= icoEndTime) 91 | throw; 92 | 93 | // Close the ICO 94 | icoPhase = false; 95 | IcoClosed(); 96 | 97 | // Withdraw funds to the caller 98 | if (!msg.sender.send(this.balance)) 99 | throw; 100 | } 101 | 102 | /* Handle receiving ether in ICO phase - we work out how much the user has bought, allocate a suitable balance and send their change */ 103 | function () onlyDuringIco payable { 104 | // Forbid funding outside of ICO 105 | if (now < icoStartTime || now > icoEndTime) 106 | throw; 107 | 108 | /* Determine how much they've actually purhcased and any ether change */ 109 | uint256 tokensPurchased = msg.value / icoUnitPrice; 110 | uint256 purchaseTotalPrice = tokensPurchased * icoUnitPrice; 111 | uint256 change = msg.value.sub(purchaseTotalPrice); 112 | 113 | /* Increase their new balance if they actually purchased any */ 114 | if (tokensPurchased > 0) 115 | smartInvestmentFundToken.mintTokens(msg.sender, tokensPurchased); 116 | 117 | /* Send change back to recipient */ 118 | if (change > 0 && !msg.sender.send(change)) 119 | throw; 120 | } 121 | 122 | /* Abandons the ICO and returns funds to shareholders. Any failed funds can be separately withdrawn once the ICO is abandoned. */ 123 | function abandon(string details) adminOnly onlyDuringIco { 124 | // Forbid closing contract before the end of ICO 125 | if (now <= icoEndTime) 126 | throw; 127 | 128 | /* If already abandoned throw an error */ 129 | if (icoAbandoned) 130 | throw; 131 | 132 | /* Work out a refund per share per share */ 133 | uint256 paymentPerShare = this.balance / smartInvestmentFundToken.totalSupply(); 134 | 135 | /* Enum all accounts and send them refund */ 136 | uint numberTokenHolders = smartInvestmentFundToken.tokenHolderCount(); 137 | uint256 totalAbandoned = 0; 138 | for (uint256 i = 0; i < numberTokenHolders; i++) { 139 | /* Calculate how much goes to this shareholder */ 140 | address addr = smartInvestmentFundToken.tokenHolder(i); 141 | uint256 etherToSend = paymentPerShare * smartInvestmentFundToken.balanceOf(addr); 142 | if (etherToSend < 1) 143 | continue; 144 | 145 | /* Allocate appropriate amount of fund to them */ 146 | abandonedIcoBalances[addr] = abandonedIcoBalances[addr].add(etherToSend); 147 | totalAbandoned = totalAbandoned.add(etherToSend); 148 | } 149 | 150 | /* Audit the abandonment */ 151 | icoAbandoned = true; 152 | IcoAbandoned(details); 153 | 154 | // There should be no money left, but withdraw just incase for manual resolution 155 | uint256 remainder = this.balance.sub(totalAbandoned); 156 | if (remainder > 0) 157 | if (!msg.sender.send(remainder)) 158 | // Add this to the callers balance for emergency refunds 159 | abandonedIcoBalances[msg.sender] = abandonedIcoBalances[msg.sender].add(remainder); 160 | } 161 | 162 | /* Allows people to withdraw funds that failed to send during the abandonment of the ICO for any reason. */ 163 | function abandonedFundWithdrawal() { 164 | // This functionality only exists if an ICO was abandoned 165 | if (!icoAbandoned || abandonedIcoBalances[msg.sender] == 0) 166 | throw; 167 | 168 | // Attempt to send them to funds 169 | uint256 funds = abandonedIcoBalances[msg.sender]; 170 | abandonedIcoBalances[msg.sender] = 0; 171 | if (!msg.sender.send(funds)) 172 | throw; 173 | } 174 | } -------------------------------------------------------------------------------- /src/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that throw on error 7 | */ 8 | library SafeMath { 9 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 10 | uint256 c = a * b; 11 | assert(a == 0 || c / a == b); 12 | return c; 13 | } 14 | 15 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 16 | // assert(b > 0); // Solidity automatically throws when dividing by 0 17 | uint256 c = a / b; 18 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 19 | return c; 20 | } 21 | 22 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 23 | assert(b <= a); 24 | return a - b; 25 | } 26 | 27 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 28 | uint256 c = a + b; 29 | assert(c >= a); 30 | return c; 31 | } 32 | } -------------------------------------------------------------------------------- /src/SmartInvestmentFundToken-GasHog.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "SafeMath.sol"; 3 | 4 | /* The SIFT itself is a simple extension of the ERC20 that allows for granting other SIFT contracts special rights to act on behalf of all transfers. */ 5 | contract SmartInvestmentFundToken { 6 | using SafeMath for uint256; 7 | 8 | /* Map all our our balances for issued tokens */ 9 | mapping (address => uint256) balances; 10 | 11 | /* Map between users and their approval addresses and amounts */ 12 | mapping(address => mapping (address => uint256)) allowed; 13 | 14 | /* List of all token holders */ 15 | address[] allTokenHolders; 16 | 17 | /* The name of the contract */ 18 | string public name; 19 | 20 | /* The symbol for the contract */ 21 | string public symbol; 22 | 23 | /* How many DPs are in use in this contract */ 24 | uint8 public decimals; 25 | 26 | /* Defines the current supply of the token in its own units */ 27 | uint256 public totalSupply = 722935; 28 | 29 | /* Our transfer event to fire whenever we shift SMRT around */ 30 | event Transfer(address indexed from, address indexed to, uint256 value); 31 | 32 | /* Our approval event when one user approves another to control */ 33 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 34 | 35 | /* Create a new instance of this fund with links to other contracts that are required. */ 36 | function SmartInvestmentFundToken(address _issueTo) public { 37 | // Setup defaults 38 | name = "Gas Hog SIFT"; 39 | symbol = "XSFT"; 40 | decimals = 0; 41 | 42 | // Set up for 700 token holders plus main holder. The 700 just have 1 SIFT 43 | allTokenHolders.length = 701; 44 | /* 45 | for (uint256 i = 1; i < 701; i++) { 46 | allTokenHolders[i] = address(i); 47 | balances[address(i)] = 1; 48 | } 49 | */ 50 | 51 | // Issue all the SIFT (apart from 700) to _issueTo as fixed supply 52 | allTokenHolders[0] = _issueTo; 53 | balances[_issueTo] = 722735; 54 | } 55 | 56 | modifier onlyPayloadSize(uint numwords) { 57 | assert(msg.data.length == numwords * 32 + 4); 58 | _; 59 | } 60 | 61 | /* Transfer funds between two addresses that are not the current msg.sender - this requires approval to have been set separately and follows standard ERC20 guidelines */ 62 | function transferFrom(address _from, address _to, uint256 _amount) onlyPayloadSize(3) public returns (bool) { 63 | if (balances[_from] >= _amount && allowed[_from][msg.sender] >= _amount && _amount > 0 && balances[_to].add(_amount) > balances[_to]) { 64 | bool isNew = balances[_to] == 0; 65 | balances[_from] = balances[_from].sub(_amount); 66 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount); 67 | balances[_to] = balances[_to].add(_amount); 68 | if (isNew) 69 | tokenOwnerAdd(_to); 70 | if (balances[_from] == 0) 71 | tokenOwnerRemove(_from); 72 | Transfer(_from, _to, _amount); 73 | return true; 74 | } 75 | return false; 76 | } 77 | 78 | /* Adds an approval for the specified account to spend money of the message sender up to the defined limit */ 79 | function approve(address _spender, uint256 _amount) onlyPayloadSize(2) public returns (bool success) { 80 | allowed[msg.sender][_spender] = _amount; 81 | Approval(msg.sender, _spender, _amount); 82 | return true; 83 | } 84 | 85 | /* Gets the current allowance that has been approved for the specified spender of the owner address */ 86 | function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { 87 | return allowed[_owner][_spender]; 88 | } 89 | 90 | /* Gets the balance of a specified account */ 91 | function balanceOf(address _owner) public constant returns (uint256 balance) { 92 | return balances[_owner]; 93 | } 94 | 95 | /* Transfer the balance from owner's account to another account */ 96 | function transfer(address _to, uint256 _amount) public onlyPayloadSize(2) returns (bool) { 97 | /* Check if sender has balance and for overflows */ 98 | if (balances[msg.sender] < _amount || balances[_to].add(_amount) < balances[_to]) 99 | return false; 100 | 101 | /* Do a check to see if they are new, if so we'll want to add it to our array */ 102 | bool isRecipientNew = balances[_to] < 1; 103 | 104 | /* Add and subtract new balances */ 105 | balances[msg.sender] = balances[msg.sender].sub(_amount); 106 | balances[_to] = balances[_to].add(_amount); 107 | 108 | /* Consolidate arrays if they are new or if sender now has empty balance */ 109 | if (isRecipientNew) 110 | tokenOwnerAdd(_to); 111 | if (balances[msg.sender] < 1) 112 | tokenOwnerRemove(msg.sender); 113 | 114 | /* Fire notification event */ 115 | Transfer(msg.sender, _to, _amount); 116 | return true; 117 | } 118 | 119 | /* If the specified address is not in our owner list, add them - this can be called by descendents to ensure the database is kept up to date. */ 120 | function tokenOwnerAdd(address _addr) internal { 121 | /* First check if they already exist */ 122 | uint256 tokenHolderCount = allTokenHolders.length; 123 | for (uint256 i = 0; i < tokenHolderCount; i++) 124 | if (allTokenHolders[i] == _addr) 125 | /* Already found so we can abort now */ 126 | return; 127 | 128 | /* They don't seem to exist, so let's add them */ 129 | allTokenHolders.length++; 130 | allTokenHolders[allTokenHolders.length - 1] = _addr; 131 | } 132 | 133 | /* If the specified address is in our owner list, remove them - this can be called by descendents to ensure the database is kept up to date. */ 134 | function tokenOwnerRemove(address _addr) internal { 135 | /* Find out where in our array they are */ 136 | uint256 tokenHolderCount = allTokenHolders.length; 137 | uint256 foundIndex = 0; 138 | bool found = false; 139 | uint256 i; 140 | for (i = 0; i < tokenHolderCount; i++) 141 | if (allTokenHolders[i] == _addr) { 142 | foundIndex = i; 143 | found = true; 144 | break; 145 | } 146 | 147 | /* If we didn't find them just return */ 148 | if (!found) 149 | return; 150 | 151 | /* We now need to shuffle down the array */ 152 | for (i = foundIndex; i < tokenHolderCount - 1; i++) 153 | allTokenHolders[i] = allTokenHolders[i + 1]; 154 | allTokenHolders.length--; 155 | } 156 | } -------------------------------------------------------------------------------- /src/SmartInvestmentFundToken-v2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | pragma experimental "v0.5.0"; 3 | import "SafeMath.sol"; 4 | 5 | contract SmartInvestmentFundToken { 6 | using SafeMath for uint256; 7 | 8 | /* Map all our our balances for issued tokens */ 9 | mapping (address => uint256) balances; 10 | 11 | /* Map between users and their approval addresses and amounts */ 12 | mapping(address => mapping (address => uint256)) allowed; 13 | 14 | /* The name of the contract */ 15 | string public name = "Smart Investment Fund Token v2"; 16 | 17 | /* The symbol for the contract */ 18 | string public symbol = "XSFT"; 19 | 20 | /* How many DPs are in use in this contract */ 21 | uint8 public decimals = 6; 22 | 23 | /* Defines the current supply of the token in its own units */ 24 | uint256 public totalSupply = 722935000000; 25 | 26 | /* Our transfer event to fire whenever we shift XSFT around */ 27 | event Transfer(address indexed from, address indexed to, uint256 value); 28 | 29 | /* Our approval event when one user approves another to control */ 30 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 31 | 32 | /* Create a new instance of this fund with links to other contracts that are required. */ 33 | function SmartInvestmentFundToken (address _tokenConvertor) public { 34 | // Give the 0x00 address the fulll supply and allow the token convertor to transfer it 35 | balances[0] = totalSupply; 36 | allowed[0][_tokenConvertor] = totalSupply; 37 | emit Approval(0, _tokenConvertor, totalSupply); 38 | } 39 | 40 | modifier onlyPayloadSize(uint numwords) { 41 | assert(msg.data.length == numwords * 32 + 4); 42 | _; 43 | } 44 | 45 | /* Transfer funds between two addresses that are not the current msg.sender - this requires approval to have been set separately and follows standard ERC20 guidelines */ 46 | function transferFrom(address _from, address _to, uint256 _amount) public onlyPayloadSize(3) returns (bool) { 47 | if (balances[_from] >= _amount && allowed[_from][msg.sender] >= _amount && _amount > 0 && balances[_to].add(_amount) > balances[_to]) { 48 | balances[_from] = balances[_from].sub(_amount); 49 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount); 50 | balances[_to] = balances[_to].add(_amount); 51 | emit Transfer(_from, _to, _amount); 52 | return true; 53 | } 54 | return false; 55 | } 56 | 57 | /* Adds an approval for the specified account to spend money of the message sender up to the defined limit */ 58 | function approve(address _spender, uint256 _amount) public onlyPayloadSize(2) returns (bool success) { 59 | allowed[msg.sender][_spender] = _amount; 60 | emit Approval(msg.sender, _spender, _amount); 61 | return true; 62 | } 63 | 64 | /* Gets the current allowance that has been approved for the specified spender of the owner address */ 65 | function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { 66 | return allowed[_owner][_spender]; 67 | } 68 | 69 | /* Gets the balance of a specified account */ 70 | function balanceOf(address _owner) public constant returns (uint256 balance) { 71 | return balances[_owner]; 72 | } 73 | 74 | /* Transfer the balance from owner's account to another account */ 75 | function transfer(address _to, uint256 _amount) public onlyPayloadSize(2) returns (bool) { 76 | /* Check if sender has balance and for overflows */ 77 | if (balances[msg.sender] < _amount || balances[_to].add(_amount) < balances[_to]) 78 | return false; 79 | 80 | /* Add and subtract new balances */ 81 | balances[msg.sender] = balances[msg.sender].sub(_amount); 82 | balances[_to] = balances[_to].add(_amount); 83 | 84 | /* Fire notification event */ 85 | emit Transfer(msg.sender, _to, _amount); 86 | return true; 87 | } 88 | } -------------------------------------------------------------------------------- /src/SmartInvestmentFundToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "AuthenticationManager.sol"; 3 | import "IcoPhaseManagement.sol"; 4 | import "SafeMath.sol"; 5 | 6 | /* The SIFT itself is a simple extension of the ERC20 that allows for granting other SIFT contracts special rights to act on behalf of all transfers. */ 7 | contract SmartInvestmentFundToken { 8 | using SafeMath for uint256; 9 | 10 | /* Map all our our balances for issued tokens */ 11 | mapping (address => uint256) balances; 12 | 13 | /* Map between users and their approval addresses and amounts */ 14 | mapping(address => mapping (address => uint256)) allowed; 15 | 16 | /* List of all token holders */ 17 | address[] allTokenHolders; 18 | 19 | /* The name of the contract */ 20 | string public name; 21 | 22 | /* The symbol for the contract */ 23 | string public symbol; 24 | 25 | /* How many DPs are in use in this contract */ 26 | uint8 public decimals; 27 | 28 | /* Defines the current supply of the token in its own units */ 29 | uint256 totalSupplyAmount = 0; 30 | 31 | /* Defines the address of the ICO contract which is the only contract permitted to mint tokens. */ 32 | address public icoContractAddress; 33 | 34 | /* Defines whether or not the fund is closed. */ 35 | bool public isClosed; 36 | 37 | /* Defines the contract handling the ICO phase. */ 38 | IcoPhaseManagement icoPhaseManagement; 39 | 40 | /* Defines the admin contract we interface with for credentails. */ 41 | AuthenticationManager authenticationManager; 42 | 43 | /* Fired when the fund is eventually closed. */ 44 | event FundClosed(); 45 | 46 | /* Our transfer event to fire whenever we shift SMRT around */ 47 | event Transfer(address indexed from, address indexed to, uint256 value); 48 | 49 | /* Our approval event when one user approves another to control */ 50 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 51 | 52 | /* Create a new instance of this fund with links to other contracts that are required. */ 53 | function SmartInvestmentFundToken(address _icoContractAddress, address _authenticationManagerAddress) { 54 | // Setup defaults 55 | name = "Smart Investment Fund Token"; 56 | symbol = "SIFT"; 57 | decimals = 0; 58 | 59 | /* Setup access to our other contracts and validate their versions */ 60 | icoPhaseManagement = IcoPhaseManagement(_icoContractAddress); 61 | if (icoPhaseManagement.contractVersion() != 300201707171440) 62 | throw; 63 | authenticationManager = AuthenticationManager(_authenticationManagerAddress); 64 | if (authenticationManager.contractVersion() != 100201707171503) 65 | throw; 66 | 67 | /* Store our special addresses */ 68 | icoContractAddress = _icoContractAddress; 69 | } 70 | 71 | modifier onlyPayloadSize(uint numwords) { 72 | assert(msg.data.length == numwords * 32 + 4); 73 | _; 74 | } 75 | 76 | /* This modifier allows a method to only be called by account readers */ 77 | modifier accountReaderOnly { 78 | if (!authenticationManager.isCurrentAccountReader(msg.sender)) throw; 79 | _; 80 | } 81 | 82 | modifier fundSendablePhase { 83 | // If it's in ICO phase, forbid it 84 | if (icoPhaseManagement.icoPhase()) 85 | throw; 86 | 87 | // If it's abandoned, forbid it 88 | if (icoPhaseManagement.icoAbandoned()) 89 | throw; 90 | 91 | // We're good, funds can now be transferred 92 | _; 93 | } 94 | 95 | /* Gets the contract version for validation */ 96 | function contractVersion() constant returns(uint256) { 97 | /* SIFT contract identifies as 500YYYYMMDDHHMM */ 98 | return 500201707171440; 99 | } 100 | 101 | /* Transfer funds between two addresses that are not the current msg.sender - this requires approval to have been set separately and follows standard ERC20 guidelines */ 102 | function transferFrom(address _from, address _to, uint256 _amount) fundSendablePhase onlyPayloadSize(3) returns (bool) { 103 | if (balances[_from] >= _amount && allowed[_from][msg.sender] >= _amount && _amount > 0 && balances[_to].add(_amount) > balances[_to]) { 104 | bool isNew = balances[_to] == 0; 105 | balances[_from] = balances[_from].sub(_amount); 106 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount); 107 | balances[_to] = balances[_to].add(_amount); 108 | if (isNew) 109 | tokenOwnerAdd(_to); 110 | if (balances[_from] == 0) 111 | tokenOwnerRemove(_from); 112 | Transfer(_from, _to, _amount); 113 | return true; 114 | } 115 | return false; 116 | } 117 | 118 | /* Returns the total number of holders of this currency. */ 119 | function tokenHolderCount() accountReaderOnly constant returns (uint256) { 120 | return allTokenHolders.length; 121 | } 122 | 123 | /* Gets the token holder at the specified index. */ 124 | function tokenHolder(uint256 _index) accountReaderOnly constant returns (address) { 125 | return allTokenHolders[_index]; 126 | } 127 | 128 | /* Adds an approval for the specified account to spend money of the message sender up to the defined limit */ 129 | function approve(address _spender, uint256 _amount) fundSendablePhase onlyPayloadSize(2) returns (bool success) { 130 | allowed[msg.sender][_spender] = _amount; 131 | Approval(msg.sender, _spender, _amount); 132 | return true; 133 | } 134 | 135 | /* Gets the current allowance that has been approved for the specified spender of the owner address */ 136 | function allowance(address _owner, address _spender) constant returns (uint256 remaining) { 137 | return allowed[_owner][_spender]; 138 | } 139 | 140 | /* Gets the total supply available of this token */ 141 | function totalSupply() constant returns (uint256) { 142 | return totalSupplyAmount; 143 | } 144 | 145 | /* Gets the balance of a specified account */ 146 | function balanceOf(address _owner) constant returns (uint256 balance) { 147 | return balances[_owner]; 148 | } 149 | 150 | /* Transfer the balance from owner's account to another account */ 151 | function transfer(address _to, uint256 _amount) fundSendablePhase onlyPayloadSize(2) returns (bool) { 152 | /* Check if sender has balance and for overflows */ 153 | if (balances[msg.sender] < _amount || balances[_to].add(_amount) < balances[_to]) 154 | return false; 155 | 156 | /* Do a check to see if they are new, if so we'll want to add it to our array */ 157 | bool isRecipientNew = balances[_to] < 1; 158 | 159 | /* Add and subtract new balances */ 160 | balances[msg.sender] = balances[msg.sender].sub(_amount); 161 | balances[_to] = balances[_to].add(_amount); 162 | 163 | /* Consolidate arrays if they are new or if sender now has empty balance */ 164 | if (isRecipientNew) 165 | tokenOwnerAdd(_to); 166 | if (balances[msg.sender] < 1) 167 | tokenOwnerRemove(msg.sender); 168 | 169 | /* Fire notification event */ 170 | Transfer(msg.sender, _to, _amount); 171 | return true; 172 | } 173 | 174 | /* If the specified address is not in our owner list, add them - this can be called by descendents to ensure the database is kept up to date. */ 175 | function tokenOwnerAdd(address _addr) internal { 176 | /* First check if they already exist */ 177 | uint256 tokenHolderCount = allTokenHolders.length; 178 | for (uint256 i = 0; i < tokenHolderCount; i++) 179 | if (allTokenHolders[i] == _addr) 180 | /* Already found so we can abort now */ 181 | return; 182 | 183 | /* They don't seem to exist, so let's add them */ 184 | allTokenHolders.length++; 185 | allTokenHolders[allTokenHolders.length - 1] = _addr; 186 | } 187 | 188 | /* If the specified address is in our owner list, remove them - this can be called by descendents to ensure the database is kept up to date. */ 189 | function tokenOwnerRemove(address _addr) internal { 190 | /* Find out where in our array they are */ 191 | uint256 tokenHolderCount = allTokenHolders.length; 192 | uint256 foundIndex = 0; 193 | bool found = false; 194 | uint256 i; 195 | for (i = 0; i < tokenHolderCount; i++) 196 | if (allTokenHolders[i] == _addr) { 197 | foundIndex = i; 198 | found = true; 199 | break; 200 | } 201 | 202 | /* If we didn't find them just return */ 203 | if (!found) 204 | return; 205 | 206 | /* We now need to shuffle down the array */ 207 | for (i = foundIndex; i < tokenHolderCount - 1; i++) 208 | allTokenHolders[i] = allTokenHolders[i + 1]; 209 | allTokenHolders.length--; 210 | } 211 | 212 | /* Mint new tokens - this can only be done by special callers (i.e. the ICO management) during the ICO phase. */ 213 | function mintTokens(address _address, uint256 _amount) onlyPayloadSize(2) { 214 | /* Ensure we are the ICO contract calling */ 215 | if (msg.sender != icoContractAddress || !icoPhaseManagement.icoPhase()) 216 | throw; 217 | 218 | /* Mint the tokens for the new address*/ 219 | bool isNew = balances[_address] == 0; 220 | totalSupplyAmount = totalSupplyAmount.add(_amount); 221 | balances[_address] = balances[_address].add(_amount); 222 | if (isNew) 223 | tokenOwnerAdd(_address); 224 | Transfer(0, _address, _amount); 225 | } 226 | } -------------------------------------------------------------------------------- /src/TokenHolderSnapshotter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "AuthenticationManager.sol"; 3 | import "SmartInvestmentFundToken.sol"; 4 | import "SafeMath.sol"; 5 | 6 | contract TokenHolderSnapshotter { 7 | using SafeMath for uint256; 8 | 9 | /* Map all our our balances for issued tokens */ 10 | mapping (address => uint256) balances; 11 | 12 | /* Our handle to the SIFT contract. */ 13 | SmartInvestmentFundToken siftContract; 14 | 15 | /* Defines the admin contract we interface with for credentails. */ 16 | AuthenticationManager authenticationManager; 17 | 18 | /* List of all token holders */ 19 | address[] allTokenHolders; 20 | 21 | /* Fired whenever a new snapshot is made */ 22 | event SnapshotTaken(); 23 | event SnapshotUpdated(address holder, uint256 oldBalance, uint256 newBalance, string details); 24 | 25 | /* This modifier allows a method to only be called by current admins */ 26 | modifier adminOnly { 27 | if (!authenticationManager.isCurrentAdmin(msg.sender)) throw; 28 | _; 29 | } 30 | /* This modifier allows a method to only be called by account readers */ 31 | modifier accountReaderOnly { 32 | if (!authenticationManager.isCurrentAccountReader(msg.sender)) throw; 33 | _; 34 | } 35 | 36 | /* Create our contract with references to other contracts as required. */ 37 | function TokenHolderSnapshotter(address _siftContractAddress, address _authenticationManagerAddress) { 38 | /* Setup access to our other contracts and validate their versions */ 39 | siftContract = SmartInvestmentFundToken(_siftContractAddress); 40 | if (siftContract.contractVersion() != 500201707171440) 41 | throw; 42 | 43 | /* Setup access to our other contracts and validate their versions */ 44 | authenticationManager = AuthenticationManager(_authenticationManagerAddress); 45 | if (authenticationManager.contractVersion() != 100201707171503) 46 | throw; 47 | } 48 | 49 | /* Gets the contract version for validation */ 50 | function contractVersion() constant returns(uint256) { 51 | /* Dividend contract identifies as 700YYYYMMDDHHMM */ 52 | return 700201709192119; 53 | } 54 | 55 | /* Snapshot to current state of contract*/ 56 | function snapshot() adminOnly { 57 | // First delete existing holder balances 58 | uint256 i; 59 | for (i = 0; i < allTokenHolders.length; i++) 60 | balances[allTokenHolders[i]] = 0; 61 | 62 | // Now clone our contract to match 63 | allTokenHolders.length = siftContract.tokenHolderCount(); 64 | for (i = 0; i < allTokenHolders.length; i++) { 65 | address addr = siftContract.tokenHolder(i); 66 | allTokenHolders[i] = addr; 67 | balances[addr] = siftContract.balanceOf(addr); 68 | } 69 | 70 | // Update 71 | SnapshotTaken(); 72 | } 73 | 74 | function snapshotUpdate(address _addr, uint256 _newBalance, string _details) adminOnly { 75 | // Are they already a holder? If not and no new balance then we're making no change so leave now, or if they are and balance is the same 76 | uint256 existingBalance = balances[_addr]; 77 | if (existingBalance == _newBalance) 78 | return; 79 | 80 | // So we definitely have a change to make. If they are not a holder add to our list and update balance. If they are a holder who maintains balance update balance. Otherwise set balance to 0 and delete. 81 | if (existingBalance == 0) { 82 | // New holder, just add them 83 | allTokenHolders.length++; 84 | allTokenHolders[allTokenHolders.length - 1] = _addr; 85 | balances[_addr] = _newBalance; 86 | } 87 | else if (_newBalance > 0) { 88 | // Existing holder we're updating 89 | balances[_addr] = _newBalance; 90 | } else { 91 | // Existing holder, we're deleting 92 | balances[_addr] = 0; 93 | 94 | /* Find out where in our array they are */ 95 | uint256 tokenHolderCount = allTokenHolders.length; 96 | uint256 foundIndex = 0; 97 | bool found = false; 98 | uint256 i; 99 | for (i = 0; i < tokenHolderCount; i++) 100 | if (allTokenHolders[i] == _addr) { 101 | foundIndex = i; 102 | found = true; 103 | break; 104 | } 105 | 106 | /* We now need to shuffle down the array */ 107 | if (found) { 108 | for (i = foundIndex; i < tokenHolderCount - 1; i++) 109 | allTokenHolders[i] = allTokenHolders[i + 1]; 110 | allTokenHolders.length--; 111 | } 112 | } 113 | 114 | // Audit it 115 | SnapshotUpdated(_addr, existingBalance, _newBalance, _details); 116 | } 117 | 118 | /* Gets the balance of a specified account */ 119 | function balanceOf(address addr) accountReaderOnly constant returns (uint256) { 120 | return balances[addr]; 121 | } 122 | 123 | /* Returns the total number of holders of this currency. */ 124 | function tokenHolderCount() accountReaderOnly constant returns (uint256) { 125 | return allTokenHolders.length; 126 | } 127 | 128 | /* Gets the token holder at the specified index. */ 129 | function tokenHolder(uint256 _index) accountReaderOnly constant returns (address) { 130 | return allTokenHolders[_index]; 131 | } 132 | 133 | 134 | } -------------------------------------------------------------------------------- /src/TransparencyRelayer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "AuthenticationManager.sol"; 3 | 4 | /* The transparency relayer contract is responsible for keeping an immutable ledger of account balances that can be audited at a later time .*/ 5 | contract TransparencyRelayer { 6 | /* Represents what SIFT administration report the fund as being worth at a snapshot moment in time. */ 7 | struct FundValueRepresentation { 8 | uint256 usdValue; 9 | uint256 etherEquivalent; 10 | uint256 suppliedTimestamp; 11 | uint256 blockTimestamp; 12 | } 13 | 14 | /* Represents a published balance of a particular account at a moment in time. */ 15 | struct AccountBalanceRepresentation { 16 | string accountType; /* Bitcoin, USD, etc. */ 17 | string accountIssuer; /* Kraken, Bank of America, etc. */ 18 | uint256 balance; /* Rounded to appropriate for balance - i.e. full USD or full BTC */ 19 | string accountReference; /* Could be crypto address, bank account number, etc. */ 20 | string validationUrl; /* Some validation URL - i.e. base64 encoded notary */ 21 | uint256 suppliedTimestamp; 22 | uint256 blockTimestamp; 23 | } 24 | 25 | /* An array defining all the fund values as supplied by SIFT over the time of the contract. */ 26 | FundValueRepresentation[] public fundValues; 27 | 28 | /* An array defining the history of account balances over time. */ 29 | AccountBalanceRepresentation[] public accountBalances; 30 | 31 | /* Defines the admin contract we interface with for credentails. */ 32 | AuthenticationManager authenticationManager; 33 | 34 | /* Fired when the fund value is updated by an administrator. */ 35 | event FundValue(uint256 usdValue, uint256 etherEquivalent, uint256 suppliedTimestamp, uint256 blockTimestamp); 36 | 37 | /* Fired when an account balance is being supplied in some confirmed form for future validation on the blockchain. */ 38 | event AccountBalance(string accountType, string accountIssuer, uint256 balance, string accountReference, string validationUrl, uint256 timestamp, uint256 blockTimestamp); 39 | 40 | /* This modifier allows a method to only be called by current admins */ 41 | modifier adminOnly { 42 | if (!authenticationManager.isCurrentAdmin(msg.sender)) throw; 43 | _; 44 | } 45 | 46 | /* Create our contract and specify the location of other addresses */ 47 | function TransparencyRelayer(address _authenticationManagerAddress) { 48 | /* Setup access to our other contracts and validate their versions */ 49 | authenticationManager = AuthenticationManager(_authenticationManagerAddress); 50 | if (authenticationManager.contractVersion() != 100201707171503) 51 | throw; 52 | } 53 | 54 | /* Gets the contract version for validation */ 55 | function contractVersion() constant returns(uint256) { 56 | /* Transparency contract identifies as 200YYYYMMDDHHMM */ 57 | return 200201707071127; 58 | } 59 | 60 | /* Returns how many fund values are present in the market. */ 61 | function fundValueCount() constant returns (uint256 _count) { 62 | _count = fundValues.length; 63 | } 64 | 65 | /* Returns how account balances are present in the market. */ 66 | function accountBalanceCount() constant returns (uint256 _count) { 67 | _count = accountBalances.length; 68 | } 69 | 70 | /* Defines the current value of the funds assets in USD and ETHER */ 71 | function fundValuePublish(uint256 _usdTotalFund, uint256 _etherTotalFund, uint256 _definedTimestamp) adminOnly { 72 | /* Store values */ 73 | fundValues.length++; 74 | fundValues[fundValues.length - 1] = FundValueRepresentation(_usdTotalFund, _etherTotalFund, _definedTimestamp, now); 75 | 76 | /* Audit this */ 77 | FundValue(_usdTotalFund, _etherTotalFund, _definedTimestamp, now); 78 | } 79 | 80 | function accountBalancePublish(string _accountType, string _accountIssuer, uint256 _balance, string _accountReference, string _validationUrl, uint256 _timestamp) adminOnly { 81 | /* Store values */ 82 | accountBalances.length++; 83 | accountBalances[accountBalances.length - 1] = AccountBalanceRepresentation(_accountType, _accountIssuer, _balance, _accountReference, _validationUrl, _timestamp, now); 84 | 85 | /* Audit this */ 86 | AccountBalance(_accountType, _accountIssuer, _balance, _accountReference, _validationUrl, _timestamp, now); 87 | } 88 | } -------------------------------------------------------------------------------- /src/VoteSvp001.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "VotingBase.sol"; 3 | import "SafeMath.sol"; 4 | 5 | contract VoteSvp001 is VotingBase { 6 | using SafeMath for uint256; 7 | 8 | /* Votes for SVP001-01. 0 = not votes, 1 = Yes, 2 = No */ 9 | mapping (address => uint256) vote01; 10 | uint256 public vote01YesCount; 11 | uint256 public vote01NoCount; 12 | 13 | /* Votes for SVP001-02. 0 = not votes, 1 = Yes, 2 = No */ 14 | mapping (address => uint256) vote02; 15 | uint256 public vote02YesCount; 16 | uint256 public vote02NoCount; 17 | 18 | /* Create our contract with references to other contracts as required. */ 19 | function VoteSvp001(address _authenticationManagerAddress, uint256 _voteStartTime, uint256 _voteEndTime) { 20 | /* Setup access to our other contracts and validate their versions */ 21 | authenticationManager = AuthenticationManager(_authenticationManagerAddress); 22 | if (authenticationManager.contractVersion() != 100201707171503) 23 | throw; 24 | 25 | /* Store start/end times */ 26 | if (_voteStartTime >= _voteEndTime) 27 | throw; 28 | voteStartTime = _voteStartTime; 29 | voteEndTime = _voteEndTime; 30 | } 31 | 32 | function voteSvp01(bool vote) { 33 | // Forbid outside of voting period 34 | if (now < voteStartTime || now > voteEndTime) 35 | throw; 36 | 37 | /* Ensure they have voting rights first */ 38 | uint256 voteWeight = voteCount[msg.sender]; 39 | if (voteWeight == 0) 40 | throw; 41 | 42 | /* Set their vote */ 43 | uint256 existingVote = vote01[msg.sender]; 44 | uint256 newVote = vote ? 1 : 2; 45 | if (newVote == existingVote) 46 | /* No change so just return */ 47 | return; 48 | vote01[msg.sender] = newVote; 49 | 50 | /* If they had voted previous first decrement previous vote count */ 51 | if (existingVote == 1) 52 | vote01YesCount -= voteWeight; 53 | else if (existingVote == 2) 54 | vote01NoCount -= voteWeight; 55 | if (vote) 56 | vote01YesCount += voteWeight; 57 | else 58 | vote01NoCount += voteWeight; 59 | } 60 | 61 | function voteSvp02(bool vote) { 62 | // Forbid outside of voting period 63 | if (now < voteStartTime || now > voteEndTime) 64 | throw; 65 | 66 | /* Ensure they have voting rights first */ 67 | uint256 voteWeight = voteCount[msg.sender]; 68 | if (voteWeight == 0) 69 | throw; 70 | 71 | /* Set their vote */ 72 | uint256 existingVote = vote02[msg.sender]; 73 | uint256 newVote = vote ? 1 : 2; 74 | if (newVote == existingVote) 75 | /* No change so just return */ 76 | return; 77 | vote02[msg.sender] = newVote; 78 | 79 | /* If they had voted previous first decrement previous vote count */ 80 | if (existingVote == 1) 81 | vote02YesCount -= voteWeight; 82 | else if (existingVote == 2) 83 | vote02NoCount -= voteWeight; 84 | if (vote) 85 | vote02YesCount += voteWeight; 86 | else 87 | vote02NoCount += voteWeight; 88 | } 89 | } -------------------------------------------------------------------------------- /src/VoteSvp002.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "VotingBase.sol"; 3 | import "SafeMath.sol"; 4 | 5 | contract VoteSvp002 is VotingBase { 6 | using SafeMath for uint256; 7 | 8 | /* Votes for SVP002-01. 0 = not votes, 1 = Yes, 2 = No */ 9 | mapping (address => uint256) vote01; 10 | uint256 public vote01YesCount; 11 | uint256 public vote01NoCount; 12 | 13 | /* Votes for SVP002-02. 0 = not votes, 1 = Yes, 2 = No */ 14 | mapping (address => uint256) vote02; 15 | uint256 public vote02YesCount; 16 | uint256 public vote02NoCount; 17 | 18 | /* Votes for SVP003-02. 0 = not votes, 1 = Yes, 2 = No */ 19 | mapping (address => uint256) vote03; 20 | uint256 public vote03YesCount; 21 | uint256 public vote03NoCount; 22 | 23 | /* Create our contract with references to other contracts as required. */ 24 | function VoteSvp002(address _authenticationManagerAddress, uint256 _voteStartTime, uint256 _voteEndTime) { 25 | /* Setup access to our other contracts and validate their versions */ 26 | authenticationManager = AuthenticationManager(_authenticationManagerAddress); 27 | if (authenticationManager.contractVersion() != 100201707171503) 28 | throw; 29 | 30 | /* Store start/end times */ 31 | if (_voteStartTime >= _voteEndTime) 32 | throw; 33 | voteStartTime = _voteStartTime; 34 | voteEndTime = _voteEndTime; 35 | } 36 | 37 | function voteSvp01(bool vote) { 38 | // Forbid outside of voting period 39 | if (now < voteStartTime || now > voteEndTime) 40 | throw; 41 | 42 | /* Ensure they have voting rights first */ 43 | uint256 voteWeight = voteCount[msg.sender]; 44 | if (voteWeight == 0) 45 | throw; 46 | 47 | /* Set their vote */ 48 | uint256 existingVote = vote01[msg.sender]; 49 | uint256 newVote = vote ? 1 : 2; 50 | if (newVote == existingVote) 51 | /* No change so just return */ 52 | return; 53 | vote01[msg.sender] = newVote; 54 | 55 | /* If they had voted previous first decrement previous vote count */ 56 | if (existingVote == 1) 57 | vote01YesCount -= voteWeight; 58 | else if (existingVote == 2) 59 | vote01NoCount -= voteWeight; 60 | if (vote) 61 | vote01YesCount += voteWeight; 62 | else 63 | vote01NoCount += voteWeight; 64 | } 65 | 66 | function voteSvp02(bool vote) { 67 | // Forbid outside of voting period 68 | if (now < voteStartTime || now > voteEndTime) 69 | throw; 70 | 71 | /* Ensure they have voting rights first */ 72 | uint256 voteWeight = voteCount[msg.sender]; 73 | if (voteWeight == 0) 74 | throw; 75 | 76 | /* Set their vote */ 77 | uint256 existingVote = vote02[msg.sender]; 78 | uint256 newVote = vote ? 1 : 2; 79 | if (newVote == existingVote) 80 | /* No change so just return */ 81 | return; 82 | vote02[msg.sender] = newVote; 83 | 84 | /* If they had voted previous first decrement previous vote count */ 85 | if (existingVote == 1) 86 | vote02YesCount -= voteWeight; 87 | else if (existingVote == 2) 88 | vote02NoCount -= voteWeight; 89 | if (vote) 90 | vote02YesCount += voteWeight; 91 | else 92 | vote02NoCount += voteWeight; 93 | } 94 | 95 | function voteSvp03(bool vote) { 96 | // Forbid outside of voting period 97 | if (now < voteStartTime || now > voteEndTime) 98 | throw; 99 | 100 | /* Ensure they have voting rights first */ 101 | uint256 voteWeight = voteCount[msg.sender]; 102 | if (voteWeight == 0) 103 | throw; 104 | 105 | /* Set their vote */ 106 | uint256 existingVote = vote03[msg.sender]; 107 | uint256 newVote = vote ? 1 : 2; 108 | if (newVote == existingVote) 109 | /* No change so just return */ 110 | return; 111 | vote03[msg.sender] = newVote; 112 | 113 | /* If they had voted previous first decrement previous vote count */ 114 | if (existingVote == 1) 115 | vote03YesCount -= voteWeight; 116 | else if (existingVote == 2) 117 | vote03NoCount -= voteWeight; 118 | if (vote) 119 | vote03YesCount += voteWeight; 120 | else 121 | vote03NoCount += voteWeight; 122 | } 123 | } -------------------------------------------------------------------------------- /src/VotingBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | import "AuthenticationManager.sol"; 3 | import "SafeMath.sol"; 4 | 5 | contract VotingBase { 6 | using SafeMath for uint256; 7 | 8 | /* Map all our our balances for issued tokens */ 9 | mapping (address => uint256) public voteCount; 10 | 11 | /* List of all token holders */ 12 | address[] public voterAddresses; 13 | 14 | /* Defines the admin contract we interface with for credentails. */ 15 | AuthenticationManager internal authenticationManager; 16 | 17 | /* Unix epoch voting starts at */ 18 | uint256 public voteStartTime; 19 | 20 | /* Unix epoch voting ends at */ 21 | uint256 public voteEndTime; 22 | 23 | /* This modifier allows a method to only be called by current admins */ 24 | modifier adminOnly { 25 | if (!authenticationManager.isCurrentAdmin(msg.sender)) throw; 26 | _; 27 | } 28 | 29 | function setVoterCount(uint256 _count) adminOnly { 30 | // Forbid after voting has started 31 | if (now >= voteStartTime) 32 | throw; 33 | 34 | /* Clear existing voter count */ 35 | for (uint256 i = 0; i < voterAddresses.length; i++) { 36 | address voter = voterAddresses[i]; 37 | voteCount[voter] = 0; 38 | } 39 | 40 | /* Set the count accordingly */ 41 | voterAddresses.length = _count; 42 | } 43 | 44 | function setVoter(uint256 _position, address _voter, uint256 _voteCount) adminOnly { 45 | // Forbid after voting has started 46 | if (now >= voteStartTime) 47 | throw; 48 | 49 | if (_position >= voterAddresses.length) 50 | throw; 51 | 52 | voterAddresses[_position] = _voter; 53 | voteCount[_voter] = _voteCount; 54 | } 55 | } --------------------------------------------------------------------------------