├── .gitignore ├── migrations ├── 1_migration.js └── 2_deploy_contracts.js ├── truffle.js ├── contracts ├── Migrations.sol └── SaintArnouldToken.sol ├── contribute.js ├── package.json ├── finalize.js ├── transfer.js ├── README.md └── test └── SaintArnouldToken.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /migrations/1_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var SaintArnouldToken = artifacts.require("./SaintArnouldToken.sol"); 2 | 3 | module.exports = function (deployer, net, accounts) { 4 | deployer.deploy(SaintArnouldToken, accounts[0], 4002944, 4066607); 5 | }; 6 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*", // Match any network id 7 | gas: 1700000, 8 | gasPrice: 20000000000 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity ^0.4.4; 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | if (msg.sender == owner) _; 10 | } 11 | 12 | function Migrations() { 13 | owner = msg.sender; 14 | } 15 | 16 | function setCompleted(uint completed) restricted { 17 | last_completed_migration = completed; 18 | } 19 | 20 | function upgrade(address new_address) restricted { 21 | Migrations upgraded = Migrations(new_address); 22 | upgraded.setCompleted(last_completed_migration); 23 | } 24 | } -------------------------------------------------------------------------------- /contribute.js: -------------------------------------------------------------------------------- 1 | //var a = abi.methodID('f', ['uint', 'uint32[]', 'bytes10', 'bytes']).toString('hex') + abi.rawEncode(['uint', 'uint32[]', 'bytes10', 'bytes'], [0x123, [0x456, 0x789], '1234567890', 'Hello, world!']).toString('hex') 2 | 3 | module.exports = function (callback) { 4 | 5 | const sender = web3.eth.accounts[1] 6 | const contract_address = "0x77499fd57915542718d184a6924e7adfc437dd3f" 7 | 8 | web3.eth.sendTransaction({ 9 | from: sender, 10 | to: contract_address, 11 | value: web3.toWei(0.1, 'ether') 12 | }, (err, result) => { 13 | if (err) { 14 | console.log(err) 15 | } else { 16 | console.log(result) 17 | } 18 | }) 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crowdfunding", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/SaintArnould/crowdfunding.git" 15 | }, 16 | "author": "", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/SaintArnould/crowdfunding/issues" 20 | }, 21 | "homepage": "https://github.com/SaintArnould/crowdfunding#readme", 22 | "dependencies": { 23 | "ethereumjs-abi": "^0.6.4", 24 | "rx": "^4.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /finalize.js: -------------------------------------------------------------------------------- 1 | var abi = require('../node_modules/ethereumjs-abi') 2 | //var a = abi.methodID('f', ['uint', 'uint32[]', 'bytes10', 'bytes']).toString('hex') + abi.rawEncode(['uint', 'uint32[]', 'bytes10', 'bytes'], [0x123, [0x456, 0x789], '1234567890', 'Hello, world!']).toString('hex') 3 | 4 | module.exports = function (callback) { 5 | 6 | const sender = web3.eth.accounts[0] 7 | 8 | var finalize = abi.methodID('finalize', []).toString('hex') 9 | 10 | const contract_address = "0x77499fd57915542718d184a6924e7adfc437dd3f" 11 | 12 | 13 | web3.eth.sendTransaction({ 14 | from: sender, 15 | to: contract_address, 16 | value: web3.toWei(0, 'ether'), 17 | data: '0x' + finalize 18 | }, (err, result) => { 19 | if (err) { 20 | console.log(err) 21 | } else { 22 | console.log(result) 23 | } 24 | }) 25 | } -------------------------------------------------------------------------------- /transfer.js: -------------------------------------------------------------------------------- 1 | var abi = require('../node_modules/ethereumjs-abi') 2 | //var a = abi.methodID('f', ['uint', 'uint32[]', 'bytes10', 'bytes']).toString('hex') + abi.rawEncode(['uint', 'uint32[]', 'bytes10', 'bytes'], [0x123, [0x456, 0x789], '1234567890', 'Hello, world!']).toString('hex') 3 | 4 | module.exports = function (callback) { 5 | 6 | const sender = web3.eth.accounts[0] 7 | 8 | var finalize = abi.methodID('transfer', ['address','uint256']).toString('hex') 9 | 10 | var data = abi.rawEncode(['address', 'uint256'], ["0xd0a69af4c3832f16067c27691a4112bda0676903", web3.toWei(2, 'ether') ]).toString('hex') 11 | 12 | const contract_address = "0x77499fd57915542718d184a6924e7adfc437dd3f" 13 | 14 | var abis = abi.rawEncode(['address', 'uint256', 'uint256'], ["0xd0a69af4c3832f16067c27691a4112bda0676903", 1272481, 1272496 ]).toString('hex') 15 | 16 | console.log(abis) 17 | 18 | web3.eth.sendTransaction({ 19 | from: sender, 20 | to: contract_address, 21 | value: web3.toWei(0, 'ether'), 22 | data: '0x' + finalize + data 23 | }, (err, result) => { 24 | if (err) { 25 | console.log(err) 26 | } else { 27 | console.log(result) 28 | } 29 | }) 30 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SaintArnould (Tokyo) Token and CrowdSale Contracts 2 | 3 | ## Getting started 4 | ICO(イニシャルコインオファリング)はEtheruem上のスマートコントラクトで自動生成されます。 5 | 6 | 1. 発行上限はなし。 1 ETH = 5000 SAT 7 | 2. セール期間は `fundingStartBlock` から `fundingEndBlock`まで 8 | 3. 発行者はセール終了後販売量の10%を追加で発行し6ヶ月間ロックする。 9 | 10 | ご質問は [@syrohei](https://twitter.com/syrohei) もしくは syrohei@gmail.com まで。 11 | 12 | 仕様環境 13 | - Truffle v3.2.5 14 | - EthereumJS TestRPC v3.0.3 15 | - Node.js v6.9.2 16 | 17 | ``` 18 | npm install 19 | truffle migrate 20 | ``` 21 | 22 | ## Contract Audit 23 | 24 | このコントラクトは現在監査中です。もしバグが見つかった場合には速やかにTwitterを通し監査報告をすることとします。 25 | 26 | 2017/7/3 17:00 - Contract Test and Deployment process test has been verified.  27 | 28 | 2017/7/3 17:04 - コントラクトのバグ調査を開始、約72時間後に監査が完了しmasterブランチにマージされる予定です。 29 | 30 | 2017/7/4 14:20 - **バグバウンティプログラムを開始、最大20ETHまでを上限にコードの監査結果を募集します。** 31 | 32 | 2017/7/7 21:00 - **スマートコントラクトはレビュー状態を終了しパブリックライブに移行しました。** 33 | 34 | ## Fnctions 35 | ``` 36 | function transfer(address _to, uint256 _value) public returns (bool) { } 37 | ``` 38 | セールで購入されたアドレスはこのfunctionをコールしてトークンを送信します。 39 | 40 | ``` 41 | function buy(address _sender) internal { } 42 | ``` 43 | 購入function。トリガーはコントラクトに着金した時 44 | 45 | ``` 46 | function finalize() external { } 47 | ``` 48 | セール終了後のコントラクト制御を制限します。また、トークン送金が可能になります。 49 | ``` 50 | function transferFounders(address _to, uint256 _value) public returns (bool) { } 51 | ``` 52 | 53 | ファウンダーのトークンはロックされており、あるblocktimeに到達しない限り送信されることはありません。 54 | ファウンダーは通常の`transfer()`を使うことができないことに注意してください。 55 | 56 | ## Test 57 | 58 | To run the tests, run a TestRPC locally with testrpc -b 1 and run this code. 59 | 60 | ``` 61 | truffle test 62 | ``` 63 | 64 | 65 | ## LICENSE 66 | 67 | MIT 68 | -------------------------------------------------------------------------------- /contracts/SaintArnouldToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | /// @title SaintArnould (Tokyo) Token (SAT) - 4 | contract SaintArnouldToken { 5 | string public constant name = "Saint Arnould Token"; 6 | string public constant symbol = "SAT"; 7 | uint8 public constant decimals = 18; // 18 decimal places, the same as ETH. 8 | 9 | uint256 public constant tokenCreationRate = 5000; //creation rate 1 ETH = 5000 SAT 10 | uint256 public constant firstTokenCap = 10 ether * tokenCreationRate; 11 | uint256 public constant secondTokenCap = 920 ether * tokenCreationRate; //27,900,000 YEN 12 | 13 | uint256 public fundingStartBlock; 14 | uint256 public fundingEndBlock; 15 | uint256 public locked_allocation; 16 | uint256 public unlockingBlock; 17 | 18 | // Receives ETH for founders. 19 | address public founders; 20 | 21 | // The flag indicates if the SAT contract is in Funding state. 22 | bool public funding_ended = false; 23 | 24 | // The current total token supply. 25 | uint256 totalTokens; 26 | 27 | mapping (address => uint256) balances; 28 | 29 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 30 | 31 | function SaintArnouldToken(address _founders, 32 | uint256 _fundingStartBlock, 33 | uint256 _fundingEndBlock) { 34 | 35 | if (_founders == 0) throw; 36 | if (_fundingStartBlock <= block.number) throw; 37 | if (_fundingEndBlock <= _fundingStartBlock) throw; 38 | 39 | founders = _founders; 40 | fundingStartBlock = _fundingStartBlock; 41 | fundingEndBlock = _fundingEndBlock; 42 | } 43 | 44 | /// @notice Transfer `_value` SAT tokens from sender's account 45 | /// `msg.sender` to provided account address `_to`. 46 | /// @param _to The address of the tokens recipient 47 | /// @param _value The amount of token to be transferred 48 | /// @return Whether the transfer was successful or not 49 | function transfer(address _to, uint256 _value) public returns (bool) { 50 | // Abort if not in Operational state. 51 | if (!funding_ended) throw; 52 | if (msg.sender == founders) throw; 53 | var senderBalance = balances[msg.sender]; 54 | if (senderBalance >= _value && _value > 0) { 55 | senderBalance -= _value; 56 | balances[msg.sender] = senderBalance; 57 | balances[_to] += _value; 58 | Transfer(msg.sender, _to, _value); 59 | return true; 60 | } 61 | return false; 62 | } 63 | 64 | function totalSupply() external constant returns (uint256) { 65 | return totalTokens; 66 | } 67 | 68 | function balanceOf(address _owner) external constant returns (uint256) { 69 | return balances[_owner]; 70 | } 71 | 72 | // Crowdfunding: 73 | 74 | /// @notice Create tokens when funding is active. 75 | /// @dev Required state: Funding Active 76 | /// @dev State transition: -> Funding Success (only if cap reached) 77 | function buy(address _sender) internal { 78 | // Abort if not in Funding Active state. 79 | if (funding_ended) throw; 80 | // The checking for blocktimes. 81 | if (block.number < fundingStartBlock) throw; 82 | if (block.number > fundingEndBlock) throw; 83 | 84 | // Do not allow creating 0 or more than the cap tokens. 85 | if (msg.value == 0) throw; 86 | 87 | var numTokens = msg.value * tokenCreationRate; 88 | totalTokens += numTokens; 89 | 90 | // Assign new tokens to the sender 91 | balances[_sender] += numTokens; 92 | 93 | // sending funds to founders 94 | founders.transfer(msg.value); 95 | 96 | // Log token creation event 97 | Transfer(0, _sender, numTokens); 98 | } 99 | 100 | /// @notice Finalize crowdfunding 101 | function finalize() external { 102 | if (block.number <= fundingEndBlock) throw; 103 | 104 | //locked allocation for founders 105 | locked_allocation = totalTokens * 10 / 100; 106 | balances[founders] = locked_allocation; 107 | totalTokens += locked_allocation; 108 | 109 | unlockingBlock = block.number + 864000; //about 6 months locked time. 110 | funding_ended = true; 111 | } 112 | 113 | function transferFounders(address _to, uint256 _value) public returns (bool) { 114 | if (!funding_ended) throw; 115 | if (block.number <= unlockingBlock) throw; 116 | if (msg.sender != founders) throw; 117 | var senderBalance = balances[msg.sender]; 118 | if (senderBalance >= _value && _value > 0) { 119 | senderBalance -= _value; 120 | balances[msg.sender] = senderBalance; 121 | balances[_to] += _value; 122 | Transfer(msg.sender, _to, _value); 123 | return true; 124 | } 125 | return false; 126 | } 127 | 128 | /// @notice If anybody sends Ether directly to this contract, consider he is 129 | function() public payable { 130 | buy(msg.sender); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /test/SaintArnouldToken.js: -------------------------------------------------------------------------------- 1 | const SaintArnouldToken = artifacts.require("SaintArnouldToken.sol"); 2 | const rx = require('rx') 3 | 4 | let meta; 5 | const sblock = 1272315 6 | const eblock = 1272343 7 | const sendETH = 50 8 | 9 | 10 | contract('SaintArnould', function (accounts) { 11 | const owner = accounts[0] 12 | const sender = accounts[1] 13 | 14 | it("should deploy ico contract", function (done) { 15 | SaintArnouldToken.deployed().then(function (instance) { 16 | meta = instance; 17 | done() 18 | }) 19 | }); 20 | it(`should be blocktime start= ${startblock} end= ${endblock}`, function (done) { 21 | meta.fundingStartBlock.call().then(function (startblock) { 22 | // console.log(startblock.toNumber()) 23 | assert.equal(startblock.toNumber(), sblock, "startblock is not match"); 24 | return meta.fundingEndBlock.call().then((function (endblock) { 25 | // console.log(endblock.toNumber()) 26 | assert.equal(endblock.toNumber(), eblock, "endblock is not match"); 27 | 28 | done() 29 | })) 30 | }) 31 | }) 32 | it("should be avalable to call a crowdsale start", function (done) { 33 | 34 | source = rx.Observable.create((observer) => { 35 | const getblock = () => { 36 | //console.log(`blocktime = ${web3.eth.blockNumber}`) 37 | meta.owner_blance = web3.eth.getBalance(owner).toNumber() 38 | 39 | web3.eth.sendTransaction({ 40 | from: sender, 41 | to: meta.address, 42 | value: web3.toWei(sendETH, 'ether'), 43 | gas: 200000, 44 | gasPrice: 50000000000 45 | }, (err, result) => { 46 | if (err) { 47 | setTimeout(() => { 48 | getblock() 49 | }, 500) 50 | } else { 51 | observer.onNext(result) 52 | 53 | } 54 | }) 55 | 56 | } 57 | getblock() 58 | 59 | }) 60 | 61 | source.subscribe(x => { 62 | 63 | meta.balanceOf.call(sender).then((balance) => { 64 | const sender_token_balance = balance.toNumber() 65 | const sender_balance = web3.eth.getBalance(sender).toNumber() 66 | const owner_eth_balance = web3.eth.getBalance(owner).toNumber() 67 | const contract_balance = web3.eth.getBalance(meta.address).toNumber() 68 | assert.equal(owner_eth_balance, meta.owner_blance + (sendETH * 1e18), "receive owner balance is not match"); 69 | assert.equal(sender_token_balance, sendETH * 5000 * 1e18, "sender token balance is not match"); 70 | assert.equal(contract_balance, 0, "contract balance is not match"); 71 | 72 | done() 73 | }) 74 | 75 | }, e => { 76 | 77 | }, () => { 78 | 79 | }) 80 | }) 81 | 82 | 83 | 84 | it("should be unable to investment for call a crowdsale ended", function (done) { 85 | 86 | source = rx.Observable.create((observer) => { 87 | const getblock = () => { 88 | //console.log(`blocktime = ${web3.eth.blockNumber}`) 89 | meta.finalize({ 90 | from: owner 91 | }).then((result) => { 92 | observer.onNext(result) 93 | 94 | }).catch((err) => { 95 | setTimeout(() => { 96 | getblock() 97 | }, 500) 98 | }) 99 | 100 | } 101 | getblock() 102 | }) 103 | source.subscribe(x => { 104 | web3.eth.sendTransaction({ 105 | from: sender, 106 | to: meta.address, 107 | value: web3.toWei(20, 'ether'), 108 | gas: 200000, 109 | gasPrice: 50000000000 110 | }, (err, result) => { 111 | if (err) { 112 | meta.funding_ended.call().then((result) => { 113 | assert.equal(result, true, "funding is not ended."); 114 | return meta.balanceOf.call(owner) 115 | }).then((result) => { 116 | const owner_token_value = result.toNumber() 117 | assert.equal(owner_token_value, sendETH * 5000 * 1e18 * 10 / 100, "funding is not ended."); 118 | done() 119 | 120 | }) 121 | } else { 122 | //observer.onNext(result) 123 | } 124 | }) 125 | }) 126 | }) 127 | 128 | it("should be unable to sendOwnerToken over six month", function (done) { 129 | source = rx.Observable.create((observer) => { 130 | const getblock = () => { 131 | // console.log(`blocktime = ${web3.eth.blockNumber}`) 132 | meta.transferFounders("0xda5c805cfcf76ccc44ba616e0898ef1e33286063", web3.toWei(20, 'ether'), { 133 | from: owner 134 | }).then((result) => { 135 | observer.onNext(result) 136 | 137 | }).catch((err) => { 138 | setTimeout(() => { 139 | getblock() 140 | }, 50) 141 | }) 142 | 143 | } 144 | getblock() 145 | }) 146 | source.subscribe(x => { 147 | meta.balanceOf.call("0xda5c805cfcf76ccc44ba616e0898ef1e33286063").then((result) => { 148 | assert.equal(result.toNumber(), 20 * 1e18, "transfer hasn't been operated."); 149 | return meta.transfer("0xda5c805cfcf76ccc44ba616e0898ef1e33286063", web3.toWei(10, 'ether'), { 150 | from: sender 151 | }) 152 | }).then((result) => { 153 | return meta.balanceOf.call("0xda5c805cfcf76ccc44ba616e0898ef1e33286063") 154 | }).then((result) => { 155 | assert.equal(result.toNumber(), 30 * 1e18, "transfer hasn't been operated."); 156 | done() 157 | }) 158 | 159 | }) 160 | }) 161 | }); --------------------------------------------------------------------------------