├── .gitignore ├── images └── station.jpeg ├── migrations ├── 3_gasstation.js ├── 1_initial_migrations.js └── 2_minime.js ├── truffle.js ├── contracts ├── IMiniMeToken.sol ├── Migrations.sol ├── Ownable.sol ├── IgasStation.sol ├── IEtherDelta.sol ├── gasStation.sol ├── etherdelta.sol └── MiniMeToken.sol ├── package.json ├── README.md ├── test ├── etherDelta.js ├── gasStationTrader.js └── gasStation-pushfill.js ├── index.js └── utility.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | wallet.json 4 | /.env 5 | .DS_Store 6 | /build 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /images/station.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swarmcity/SCLabs-gasstation-service/HEAD/images/station.jpeg -------------------------------------------------------------------------------- /migrations/3_gasstation.js: -------------------------------------------------------------------------------- 1 | var gasStation = artifacts.require("gasStation"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(gasStation); 5 | }; -------------------------------------------------------------------------------- /migrations/1_initial_migrations.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 18545, 6 | network_id: "*" // Match any network id 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /contracts/IMiniMeToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | contract IMiniMeToken { 4 | function transfer(address _to, uint256 _amount) returns (bool success); 5 | function transferFrom(address _from, address _to, uint256 _amount) returns (bool success); 6 | function balanceOf(address _owner) constant returns (uint256 balance); 7 | function approve(address _spender, uint256 _amount) returns (bool success); 8 | function allowance(address _owner, address _spender) returns (uint256 remaining); 9 | } -------------------------------------------------------------------------------- /migrations/2_minime.js: -------------------------------------------------------------------------------- 1 | var MiniMeTokenFactory = artifacts.require("MiniMeTokenFactory"); 2 | var MiniMeToken = artifacts.require("MiniMeToken"); 3 | var gasStation = artifacts.require("gasStation"); 4 | 5 | module.exports = function(deployer) { 6 | deployer.deploy(MiniMeTokenFactory).then(function() { 7 | deployer.deploy(MiniMeToken, MiniMeTokenFactory.address, 0, 8 | 0, 9 | "Swarm City Token", 10 | 18, 11 | "SWT", 12 | true); 13 | 14 | }); 15 | deployer.deploy(gasStation); 16 | }; -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | 3 | 4 | /* 5 | * Ownable 6 | * 7 | * Base contract with an owner. 8 | * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner. 9 | */ 10 | contract Ownable { 11 | address public owner; 12 | 13 | function Ownable() { 14 | owner = msg.sender; 15 | } 16 | 17 | modifier onlyOwner() { 18 | if (msg.sender != owner) { 19 | throw; 20 | } 21 | _; 22 | } 23 | 24 | function transferOwnership(address newOwner) onlyOwner { 25 | if (newOwner != address(0)) { 26 | owner = newOwner; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /contracts/IgasStation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | contract IgasStation { 4 | 5 | // inherited 6 | function owner() public constant returns(address); 7 | function transferOwnership(address newOwner); 8 | 9 | function tokenreceiver() public constant returns(address); 10 | function pullfill(address _token_address, uint _valid_until,uint _random,uint _take ,uint _give,uint8 _v, bytes32 _r, bytes32 _s); 11 | function pushfill(address _token_address, uint _valid_until,uint _random,uint _take ,uint _give,address gastankclient, uint8 _v, bytes32 _r, bytes32 _s); 12 | function changeTokenReceiver(address _newTokenReceiver); 13 | function withdrawETH(address to); 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sc-gasstation", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "start": "node index.js", 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "browserify": "browserify utility.js --standalone utility > gs-client/utility.js && browserify node_modules/keythereum/index.js --standalone keythereum > gs-client/keythereum.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/swarmcity/sc-gasstation.git" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/swarmcity/sc-gasstation/issues" 22 | }, 23 | "dependencies": { 24 | "bignumber.js": "4.0.1", 25 | "bitcore-mnemonic": "github:claykohut/bitcore-mnemonic", 26 | "body-parser": "^1.17.2", 27 | "cors": "^2.7.1", 28 | "dotenv": "^4.0.0", 29 | "eth-lightwallet": "git+https://github.com/BlockTube/eth-lightwallet.git", 30 | "ethereumjs-tx": "1.1.1", 31 | "ethereumjs-util": "^4.5.0", 32 | "express": "^4.13.4", 33 | "hooked-web3-provider": "^1.0.0", 34 | "js-sha256": "^0.6.0", 35 | "keythereum": "0.2.6", 36 | "lodash": "^4.11.1", 37 | "memory-cache": "^0.2.0", 38 | "request": "^2.72.0", 39 | "sha3": "^1.2.0", 40 | "web3": "^0.15.3" 41 | }, 42 | "homepage": "https://github.com/swarmcity/sc-gasstation#readme" 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sc-gasstation 2 | 3 | An experimental service providing a way to exchange ERC20 tokens in ETH to pay for future transactions. 4 | 5 | 6 | # Introduction 7 | 8 | We presented the gasstation on DEVCON3 (Nov 4th 2017). You can view the video here : 9 | 10 | [![SwarmCity GasStation on DEVCON3](https://img.youtube.com/vi/ItAe8CNuY-I/0.jpg)](https://youtu.be/ItAe8CNuY-I?t=8m28s) 11 | 12 | 13 | # Installing 14 | 15 | ``` 16 | $ npm install -g ethereumjs-testrpc 17 | $ npm install -g generate-contract-interface 18 | $ npm install -g browserify 19 | $ npm install 20 | ``` 21 | 22 | Now create contract interfaces and browserify utility library 23 | 24 | ``` 25 | . ./build.sh 26 | ``` 27 | 28 | 29 | # Running Truffle tests 30 | 31 | ## Gasstation using the push-fill ( where gasstation service triggers the tokens->ETH exchange ) 32 | 33 | ``` 34 | $ truffle test test/gasStation-pushfill.js 35 | ``` 36 | 37 | # Running the gasstation API service 38 | 39 | In one terminal - start testRPC 40 | 41 | ``` 42 | $ testrpc -p 18546 43 | ``` 44 | 45 | in another terminal - create a file ```.env``` containing these parameters : 46 | 47 | 48 | ``` 49 | privatekey="(a private key holding the ETH to supply the upfront gas)" 50 | gastankaddress="(the address of the gastank contract)" 51 | erc20token="(the address of the ERC20 token your gastank accepts)" 52 | PORT=3000 53 | ``` 54 | 55 | Now start up your gastank 56 | 57 | ``` 58 | nodemon 59 | ``` 60 | 61 | or 62 | 63 | ``` 64 | node index.js 65 | ``` 66 | 67 | # Frontend 68 | 69 | A sample frontend that uses the gastank API can be found here : https://github.com/swarmcity/sc-gasstationclient 70 | 71 | 72 | ![Fill me up](images/station.jpeg) 73 | 74 | -------------------------------------------------------------------------------- /contracts/IEtherDelta.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.9; 2 | 3 | contract IEtherDelta { 4 | function admin() public constant returns(address); 5 | function feeAccount() public constant returns(address); 6 | function accountLevelsAddr() public constant returns(address); 7 | function feeMake() public constant returns(uint); 8 | function feeTake() public constant returns(uint); 9 | function feeRebate() public constant returns(uint); 10 | // function tokens() public constant returns([object Object]); 11 | // function orders() public constant returns([object Object]); 12 | // function orderFills() public constant returns([object Object]); 13 | function changeAdmin(address admin_); 14 | function changeAccountLevelsAddr(address accountLevelsAddr_); 15 | function changeFeeAccount(address feeAccount_); 16 | function changeFeeMake(uint feeMake_); 17 | function changeFeeTake(uint feeTake_); 18 | function changeFeeRebate(uint feeRebate_); 19 | function deposit() payable; 20 | function withdraw(uint amount); 21 | function depositToken(address token, uint amount); 22 | function withdrawToken(address token, uint amount); 23 | function balanceOf(address token, address user) constant returns (uint); 24 | function order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce); 25 | function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount); 26 | function testTrade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount, address sender) constant returns(bool); 27 | function availableVolume(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint); 28 | function amountFilled(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint); 29 | function cancelOrder(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, uint8 v, bytes32 r, bytes32 s); 30 | } 31 | -------------------------------------------------------------------------------- /contracts/gasStation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import './IMiniMeToken.sol'; 4 | import './Ownable.sol'; 5 | 6 | contract gasStation is Ownable { 7 | 8 | // track used fillup hashes 9 | mapping(bytes32=>bool) usedhashes; 10 | 11 | address public tokenreceiver; 12 | address public franchisee; 13 | 14 | event NewHash(bytes32 h,address token_address, address gastankaddress, uint take, uint give, uint valid, uint random); 15 | 16 | // constructor 17 | function gasStation(address _tokenreceiver, address _franchisee) payable { 18 | 19 | tokenreceiver = _tokenreceiver; 20 | franchisee = _franchisee; 21 | 22 | } 23 | 24 | // default function 25 | function() payable {} 26 | 27 | // exchange by client ( hash signed by gasstation ) 28 | function pullFill(address _token_address, uint _valid_until, uint _random, uint _take, uint _give, uint8 _v, bytes32 _r, bytes32 _s){ 29 | 30 | bytes32 hash = sha256(_token_address, this, msg.sender, _take,_give, _valid_until, _random); 31 | 32 | NewHash(hash, _token_address, this, _take, _give, _valid_until, _random); 33 | 34 | require ( 35 | usedhashes[hash] != true 36 | && (ecrecover(hash, _v, _r, _s) == owner) 37 | && block.number <= _valid_until 38 | ); 39 | 40 | // claim tokens 41 | IMiniMeToken token = IMiniMeToken(_token_address); 42 | 43 | require(token.transferFrom(msg.sender,tokenreceiver,_give)); 44 | 45 | // send ETH (gas) 46 | msg.sender.transfer(_take); 47 | 48 | // invalidate this deal's hash 49 | usedhashes[hash] = true; 50 | 51 | } 52 | 53 | // exchange by server ( hash signed by client ) 54 | function pushFill(address _token_address, uint _valid_until, uint _random, uint _take, uint _give, address gastankclient, uint8 _v, bytes32 _r, bytes32 _s) onlyOwner { 55 | 56 | bytes32 hash = sha256(_token_address, this, gastankclient, _take, _give, _valid_until, _random); 57 | 58 | NewHash(hash, _token_address, this, _take, _give, _valid_until, _random); 59 | 60 | require ( 61 | usedhashes[hash] != true 62 | && 63 | (ecrecover(hash, _v, _r, _s) == gastankclient) 64 | && block.number <= _valid_until 65 | ); 66 | 67 | // claim tokens 68 | IMiniMeToken token = IMiniMeToken(_token_address); 69 | 70 | require(token.transferFrom(gastankclient, tokenreceiver, _give)); 71 | 72 | // send ETH (gas) 73 | gastankclient.transfer(_take); 74 | 75 | // invalidate this deal's hash 76 | usedhashes[hash] = true; 77 | 78 | } 79 | 80 | function changeTokenReceiver(address _newTokenReceiver) onlyOwner { 81 | tokenreceiver = _newTokenReceiver; 82 | } 83 | 84 | function changeFranchisee(address _franchisee) onlyOwner { 85 | franchisee = _franchisee; 86 | } 87 | 88 | function withdrawETH() { 89 | require(msg.sender == franchisee); 90 | require(franchisee.send(this.balance)); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /test/etherDelta.js: -------------------------------------------------------------------------------- 1 | var MiniMeTokenFactory = artifacts.require("./MiniMeToken.sol"); 2 | var MiniMeToken = artifacts.require("./MiniMeToken.sol"); 3 | //var gasStation = artifacts.require("./gasStation.sol"); 4 | var etherDelta = artifacts.require("./etherdelta.sol"); 5 | var utility = require('../utility.js')(); 6 | const sha256 = require('js-sha256').sha256; 7 | const sha3 = require('web3/lib/utils/sha3.js'); 8 | 9 | 10 | contract('Token Setup', function(accounts) { 11 | 12 | 13 | var ETHseller = accounts[3]; 14 | var gastankInstance = accounts[4]; 15 | // fill in the private key of gastankInstance here 16 | var gastankInstancePrivateKey = 'ab802b1dce45b32047f71f21eb1aef8713b52962f773255d49653d1465424146'; 17 | 18 | 19 | var miniMeTokenFactory; 20 | var swtToken; 21 | // var hashtagRepToken; 22 | var gasStationInstance; 23 | var etherDeltaInstance; 24 | 25 | // gasstation params 26 | var _triggercost = 2137880000000000; 27 | var _exchangerate_numerator = 1; 28 | var _exchangerate_denominator = 1; 29 | 30 | var _tokensToExchange = 2137880000000000; 31 | 32 | var self = this; 33 | 34 | describe('Deploy MiniMeToken TokenFactory', function() { 35 | it("should deploy MiniMeToken contract", function(done) { 36 | MiniMeTokenFactory.new().then(function(_miniMeTokenFactory) { 37 | assert.ok(_miniMeTokenFactory.address); 38 | miniMeTokenFactory = _miniMeTokenFactory; 39 | console.log('miniMeTokenFactory created at address', _miniMeTokenFactory.address); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | describe('Deploy SWT (test) Token', function() { 46 | it("should deploy a MiniMeToken contract", function(done) { 47 | MiniMeToken.new( 48 | miniMeTokenFactory.address, 49 | 0, 50 | 0, 51 | "Swarm City Token", 52 | 18, 53 | "SWT", 54 | true 55 | ).then(function(_miniMeToken) { 56 | assert.ok(_miniMeToken.address); 57 | console.log('SWT token created at address', _miniMeToken.address); 58 | swtToken = _miniMeToken; 59 | done(); 60 | }); 61 | }); 62 | 63 | it("should mint tokens for gastankInstance", function(done) { 64 | swtToken.generateTokens(gastankInstance, 1 * 1e18).then(function() { 65 | done(); 66 | }); 67 | }); 68 | 69 | }); 70 | 71 | describe('etherDelta setup', function() { 72 | // function etherDelta(address admin_, address feeAccount_, address accountLevelsAddr_, uint feeMake_, uint feeTake_, uint feeRebate_) { 73 | //admin = admin_; 74 | 75 | it("should deploy etherDelta", function(done) { 76 | etherDelta.new(accounts[0], accounts[0], 0x0, 0, 0, 0, { 77 | gas: 4700000, 78 | }).then(function(instance) { 79 | etherDeltaInstance = instance; 80 | assert.isNotNull(etherDeltaInstance); 81 | done(); 82 | }); 83 | }); 84 | 85 | it("ETHseller should deposit 1 ETH to EtherDelta", function(done) { 86 | 87 | etherDeltaInstance.deposit({ 88 | from: ETHseller, 89 | value: 1 * 1e18 90 | }).then(function(r) { 91 | done(); 92 | }); 93 | }); 94 | 95 | 96 | it("ETHseller should have 1 ETH in the EtherDelta contract", function(done) { 97 | etherDeltaInstance.balanceOf.call(0, ETHseller).then(function(balance) { 98 | assert.equal(balance.valueOf(), 1 * 1e18, "account not correct amount"); 99 | done(); 100 | }); 101 | }); 102 | 103 | it("gastankInstance should give allowance for 1 SWT to EtherDelta", function(done) { 104 | swtToken.approve(etherDeltaInstance.address, 1 * 1e18, { 105 | from: gastankInstance, 106 | }).then(function(r) { 107 | done(); 108 | }); 109 | }); 110 | 111 | it("gastankInstance should deposit 1 SWT to EtherDelta", function(done) { 112 | etherDeltaInstance.depositToken(swtToken.address, 1 * 1e18, { 113 | from: gastankInstance, 114 | }).then(function(r) { 115 | done(); 116 | }); 117 | }); 118 | 119 | it("gastankInstance should have 1 SWT in the EtherDelta contract", function(done) { 120 | etherDeltaInstance.balanceOf.call(swtToken.address, gastankInstance).then(function(balance) { 121 | assert.equal(balance.valueOf(), 1 * 1e18, "account not correct amount"); 122 | done(); 123 | }); 124 | }); 125 | 126 | 127 | 128 | it("should place a sell order ETH/SWT etherDelta", function(done) { 129 | 130 | var watcher = etherDeltaInstance.Order(); 131 | watcher.watch(function(error, result) { 132 | console.log('new order event ', result.args); 133 | watcher.stopWatching(); 134 | done(); 135 | }); 136 | 137 | //function order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce) { 138 | etherDeltaInstance.order( 139 | swtToken.address, // SWT address 140 | 1 * 1e18, // amount of SWT I wanna buy 141 | 0, // 0 = ETH 142 | 1 * 1e18, // Amount of ETH I wanna sell 143 | 10000, // validity 144 | 1, // nonce 145 | { 146 | from: ETHseller 147 | }).then(function(r) { 148 | // this will fire an Order event... 149 | }); 150 | }); 151 | 152 | 153 | 154 | // function availableVolume(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint) { 155 | // bytes32 hash = 156 | 157 | 158 | it("Available volume of order should be 1 * 1e18", function(done) { 159 | 160 | 161 | 162 | const condensed = utility.pack( 163 | [ 164 | etherDeltaInstance.address, //'this.config.contractEtherDeltaAddr, 165 | swtToken.address, //tokenGet, 166 | 1 * 1e18, //amountGet, 167 | 0, //tokenGive, 168 | 1 * 1e18, //amountGive, 169 | 10000, //expires, 170 | 1 // orderNonce, 171 | ], [160, 160, 256, 160, 256, 256, 256]); 172 | const hash = sha256(new Buffer(condensed, 'hex')); 173 | 174 | console.log('hash=', hash); 175 | 176 | utility.sign(gastankInstance, hash, gastankInstancePrivateKey, function(err, signature) { 177 | if (err) { 178 | console.log('ERR', err); 179 | return done(); 180 | } 181 | console.log('sig=', signature); 182 | 183 | etherDeltaInstance.availableVolume.call( 184 | swtToken.address, 185 | 1 * 1e18, 186 | 0, 187 | 1 * 1e18, 188 | 10000, 189 | 1, 190 | ETHseller, 191 | signature.v, 192 | signature.r, 193 | signature.s 194 | 195 | ).then(function(volume) { 196 | assert.equal(volume.valueOf(), 1 * 1e18, "volume not correct amount"); 197 | done(); 198 | }); 199 | }); 200 | 201 | }); 202 | 203 | 204 | it("should buy ETH for SWT", function(done) { 205 | 206 | // function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount) { 207 | 208 | const condensed = utility.pack( 209 | [ 210 | etherDeltaInstance.address, //'this.config.contractEtherDeltaAddr, 211 | swtToken.address, //tokenGet, 212 | 1 * 1e18, //amountGet, 213 | 0, //tokenGive, 214 | 1 * 1e18, //amountGive, 215 | 10000, //expires, 216 | 1 // orderNonce, 217 | ], [160, 160, 256, 160, 256, 256, 256]); 218 | const hash = sha256(new Buffer(condensed, 'hex')); 219 | 220 | console.log('hash=', hash); 221 | 222 | utility.sign(gastankInstance, hash, gastankInstancePrivateKey, function(err, signature) { 223 | if (err) { 224 | console.log('ERR', err); 225 | return done(); 226 | } 227 | console.log('sig=', signature); 228 | 229 | function prefixMessage(msgIn) { 230 | let msg = msgIn; 231 | msg = new Buffer(msg.slice(2), 'hex'); 232 | msg = Buffer.concat([ 233 | new Buffer(`\x19Ethereum Signed Message:\n${msg.length.toString()}`), 234 | msg 235 | ]); 236 | console.log('MSG TO BE HASHED 2', msg.toString('hex')); 237 | msg = sha3(`0x${msg.toString('hex')}`, { 238 | encoding: 'hex' 239 | }); 240 | console.log('HASH 2', msg.toString('hex')); 241 | msg = new Buffer((msg.substring(0, 2) === '0x') ? msg.slice(2) : msg, 'hex'); 242 | return `0x${msg.toString('hex')}`; 243 | } 244 | 245 | utility.verify(gastankInstance, signature.v, signature.r, signature.s, prefixMessage('0x' + hash), function(err, res) { 246 | if (err) { 247 | console.log('verify err', err); 248 | } 249 | console.log('verify result=', res); 250 | 251 | etherDeltaInstance.trade( 252 | swtToken.address, 253 | 1 * 1e18, 254 | 0, 255 | 1 * 1e18, 256 | 10000, 257 | 1, 258 | ETHseller, 259 | signature.v, 260 | signature.r, 261 | signature.s, 1 * 1e10, { 262 | from: gastankInstance 263 | }).then(function(r) { 264 | done(); 265 | }); 266 | }); 267 | 268 | }); 269 | }); 270 | 271 | 272 | it("ETHseller should now have 1 SWT in the EtherDelta contract", function(done) { 273 | etherDeltaInstance.balanceOf.call(swtToken.address, ETHseller).then(function(balance) { 274 | assert.equal(balance.valueOf(), 1 * 1e10, "account not correct amount"); 275 | done(); 276 | }); 277 | }); 278 | 279 | }); 280 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyparser = require('body-parser'); 3 | var app = express(); 4 | var cors = require('cors'); 5 | var Web3 = require('web3'); 6 | var HookedWeb3Provider = require("hooked-web3-provider"); 7 | var EthJStx = require("ethereumjs-tx"); 8 | var ethUtil = require("ethereumjs-util"); 9 | var lightwallet = require("eth-lightwallet"); 10 | //var config = require('./config.json'); 11 | const sha256 = require('js-sha256').sha256; 12 | var cache = require('memory-cache'); 13 | var request = require('request'); 14 | // contracts 15 | var gasStation = require('./build/contracts/gasStation.json'); 16 | var IMiniMeToken = require('./build/contracts/IMiniMeToken.json'); 17 | 18 | require('dotenv').config(); 19 | 20 | 21 | console.log(process.env.PRIVKEY); 22 | process.exit(); 23 | 24 | var secretSeed = lightwallet.keystore.generateRandomSeed(); 25 | 26 | var utility = require('./utility.js')(); 27 | 28 | // check for valid Eth address 29 | function isAddress(address) { 30 | return /^(0x)?[0-9a-f]{40}$/i.test(address); 31 | }; 32 | 33 | // Add 0x to address 34 | function fixaddress(address) { 35 | // Strip all spaces 36 | address = address.replace(' ', ''); 37 | 38 | //console.log("Fix address", address); 39 | if (!strStartsWith(address, '0x')) { 40 | return ('0x' + address); 41 | } 42 | return address; 43 | } 44 | 45 | function strStartsWith(str, prefix) { 46 | return str.indexOf(prefix) === 0; 47 | } 48 | 49 | var gastankAccount; 50 | var web3; 51 | 52 | password = '1234'; 53 | 54 | lightwallet.keystore.deriveKeyFromPassword(password, function(err, pwDerivedKey) { 55 | 56 | var seed = 'unhappy nerve cancel reject october fix vital pulse cash behind curious bicycle'; 57 | var ks = new lightwallet.keystore(seed, pwDerivedKey); 58 | 59 | console.log('importing pk ', process.env.privatekey); 60 | ks.importPrivateKey(process.env.privatekey, pwDerivedKey); 61 | 62 | gastankAccount = ethUtil.addHexPrefix(ks.getAddresses()[0]); 63 | console.log(gastankAccount); 64 | 65 | var web3Provider = new HookedWeb3Provider({ 66 | host: process.env.web3host, 67 | transaction_signer: ks 68 | }); 69 | 70 | ks.passwordProvider = function(callback) { 71 | callback(null, password); 72 | }; 73 | 74 | console.log("Wallet initted addr=" + ks.getAddresses()[0]); 75 | 76 | web3 = new Web3(web3Provider); 77 | 78 | web3.eth.getBalance(gastankAccount, function(err, res) { 79 | if (err) { 80 | return console.log(err); 81 | } 82 | console.log('gastank-server (', gastankAccount, ') has', res.toString(10), 'wei / ', web3.fromWei(res, "ether").toString(10), 'ETH'); 83 | }); 84 | 85 | // start webserver... 86 | app.listen(process.env.PORT, function() { 87 | console.log('server listening on port ', process.env.PORT); 88 | }); 89 | }); 90 | 91 | var allowCrossDomain = function(req, res, next) { 92 | res.header('Access-Control-Allow-Origin', '*'); 93 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); 94 | res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); 95 | 96 | // intercept OPTIONS method 97 | if ('OPTIONS' == req.method) { 98 | res.sendStatus(200); 99 | } else { 100 | next(); 101 | } 102 | }; 103 | app.use(allowCrossDomain); 104 | app.use(bodyparser.json()); 105 | 106 | function getprice(token, cb) { 107 | 108 | var cachekey = 'price-' + token; 109 | 110 | var r = cache.get(cachekey); 111 | if (r) { 112 | cb(null, r); 113 | } else { 114 | request('https://api.coinmarketcap.com/v1/ticker/' + token + '/?convert=ETH', function(error, response, body) { 115 | if (error && (response || response.statusCode !== 200)) { 116 | return cb(error); 117 | } 118 | var p = JSON.parse(body); 119 | cache.put(cachekey, [Object.assign({}, p[0], { 120 | cached_at: new Date() 121 | })], 60 * 1000); 122 | cb(null, p); 123 | }); 124 | } 125 | } 126 | 127 | function getgasprice(cb) { 128 | request('http://ethgasstation.info/json/ethgasAPI.json', function(error, response, body) { 129 | if (error && (response || response.statusCode !== 200)) { 130 | return cb(error); 131 | } 132 | var p = JSON.parse(body); 133 | cb(null, Math.ceil(p.safeLow * 1e9)); 134 | }); 135 | } 136 | 137 | app.get('/tokens', function(req, res) { 138 | res.status(200).json({ 139 | "swarm-city": process.env.erc20token 140 | }); 141 | }); 142 | 143 | app.get('/abi', function(req, res) { 144 | res.status(200).json({ 145 | erc20: IMiniMeToken.abi, 146 | gasstation: gasStation.abi, 147 | }); 148 | }); 149 | 150 | // this is an estimate.... 151 | upfrontgas = 100000 + 200000; 152 | 153 | app.get('/price', function(req, res) { 154 | 155 | var tokensymbol; 156 | 157 | switch (req.query.tokenaddress) { 158 | case process.env.erc20token: // testRPC 159 | tokensymbol = 'swarm-city'; 160 | break; 161 | default: 162 | var err = 'unknown token address ' + req.query.tokenaddress + ' - I only know ' + process.env.erc20token 163 | return res.status(500).json({ 164 | error: err 165 | }); 166 | break; 167 | } 168 | 169 | getgasprice(function(error, gasPrice) { 170 | 171 | getprice(tokensymbol, function(error, p) { 172 | if (error) { 173 | return res.status(500).json({ 174 | error: error 175 | }); 176 | } 177 | 178 | web3.eth.getBlockNumber(function(error, blockNumber) { 179 | 180 | var valid_until = blockNumber + 5; 181 | var price = Math.floor(p[0].price_eth * 1e18); 182 | 183 | var resp = { 184 | token_address: req.query.tokenaddress, 185 | gasprice: gasPrice, 186 | price: price, 187 | valid_until: valid_until, 188 | }; 189 | 190 | console.log('requested price'); 191 | console.log(resp); 192 | res.status(200).json(resp); 193 | }.bind(this)); 194 | }.bind(this)); 195 | }.bind(this)); 196 | }); 197 | 198 | /** 199 | * { 200 | * take: , 201 | * from:
202 | * tokenaddress: 203 | * } 204 | */ 205 | app.post('/sign', function(req, res) { 206 | 207 | var tokensymbol; 208 | 209 | console.log(req.body); 210 | 211 | switch (req.body.tokenaddress) { 212 | case process.env.erc20token: // testRPC 213 | tokensymbol = 'swarm-city'; 214 | break; 215 | default: 216 | var err = 'unknown token address ' + req.body.tokenaddress + ' - I only know ' + process.env.erc20token 217 | console.log(err); 218 | return res.status(500).json({ 219 | error: err 220 | }); 221 | break; 222 | } 223 | 224 | getgasprice(function(error, gasPrice) { 225 | 226 | getprice(tokensymbol, function(error, p) { 227 | if (error) { 228 | return res.status(500).json({ 229 | error: error 230 | }); 231 | } 232 | 233 | web3.eth.getBlockNumber(function(error, blockNumber) { 234 | 235 | var to = process.env.gastankaddress; 236 | var valid_until = blockNumber + 5; 237 | var from = req.body.from; 238 | var give = Math.floor(req.body.take * parseFloat(process.env.pricemargin) / p[0].price_eth) - upfrontgas * gasPrice; 239 | var random = Math.floor(Math.random() * 100000000000); 240 | 241 | const condensed = utility.pack( 242 | [ 243 | req.body.tokenaddress, 244 | to, 245 | from, 246 | req.body.take, 247 | give, 248 | valid_until, 249 | random, 250 | ], [160, 160, 160, 256, 256, 256, 256]); 251 | const hash = sha256(new Buffer(condensed, 'hex')); 252 | 253 | const sig = ethUtil.ecsign( 254 | new Buffer(hash, 'hex'), 255 | new Buffer(process.env.privatekey, 'hex')); 256 | const r = `0x${sig.r.toString('hex')}`; 257 | const s = `0x${sig.s.toString('hex')}`; 258 | const v = sig.v; 259 | const result = { 260 | r, 261 | s, 262 | v 263 | }; 264 | 265 | var resp = { 266 | token_address: req.body.tokenaddress, 267 | gasprice: gasPrice, 268 | take: req.body.take, 269 | give: give, 270 | to: to, 271 | valid_until: valid_until, 272 | random: random, 273 | sig: result 274 | }; 275 | 276 | console.log('requested signing'); 277 | console.log(resp); 278 | res.status(200).json(resp); 279 | }.bind(this)); 280 | }.bind(this)); 281 | }.bind(this)); 282 | }); 283 | 284 | 285 | app.post('/fillup', function(req, res) { 286 | console.log('hallo'); 287 | console.log(req.body); 288 | 289 | var decodetx = new EthJStx(req.body.tx1); 290 | 291 | console.log('tx=', decodetx); 292 | console.log('gas tostring=', decodetx.gas.toString('hex')); 293 | 294 | var txGas = web3.toBigNumber(ethUtil.addHexPrefix(decodetx.gas.toString('hex'))); 295 | var gasTankClientAddress = ethUtil.addHexPrefix(decodetx.from.toString('hex')); 296 | 297 | 298 | console.log('txGas=', txGas); 299 | console.log('from=', gasTankClientAddress); 300 | 301 | console.log('gastank account=', gastankAccount); 302 | console.log('sending', upfrontgas * 2 * 1e9, 'Wei / ', web3.fromWei(upfrontgas * 2 * 1e9, "ether").toString(10), 'ETH from ', gastankAccount, 'to', gasTankClientAddress); 303 | 304 | getgasprice(function(error, gasPrice) { 305 | web3.eth.sendTransaction({ 306 | from: gastankAccount, 307 | to: gasTankClientAddress, 308 | value: upfrontgas * 2 * 1e9, 309 | gasPrice: gasPrice, 310 | gas: 50000, 311 | }, function(err, txhash) { 312 | console.log('sent gas - txhash = ', err, txhash); 313 | 314 | web3.eth.getBalance(gastankAccount, function(err, balance) { 315 | console.log('ETH balance of gastank (', gastankAccount, ') is', balance.toString(10) / 1e18); 316 | web3.eth.getBalance(gasTankClientAddress, function(err, balance) { 317 | console.log('ETH balance of gasTankClientAddress (', gasTankClientAddress, ') is', balance.toString(10) / 1e18); 318 | console.log('-----++++++-----++++++-----++++++-----++++++-----++++++-----'); 319 | //inject allowance TX 320 | console.log('-----ALLOW TX-----'); 321 | web3.eth.sendRawTransaction(req.body.tx1, function(err, res) { 322 | console.log('create allowance - tx sent', err, res); 323 | }) 324 | 325 | console.log('-----/ALLOW TX-----'); 326 | console.log('-----FILL TX-----'); 327 | 328 | decodetx = new EthJStx(req.body.tx2); 329 | //console.log(decodetx); 330 | var txTo = ethUtil.addHexPrefix(decodetx.to.toString('hex')); 331 | //txGas = web3.toBigNumber('0x' + decodetx.gas.toString('hex')); 332 | //txGasPrice = web3.toBigNumber('0x' + decodetx.gasPrice.toString('hex')); 333 | //weiNeeded = txGas.add(1000000).mul(txGasPrice).toNumber(10); 334 | gasTankClientAddress = ethUtil.addHexPrefix(decodetx.from.toString('hex')); 335 | 336 | 337 | console.log('txTo=', txTo); 338 | //console.log('txGas=', txGas); 339 | //console.log('txGasPrice=', txGasPrice); 340 | //console.log('weiNeeded=', weiNeeded); 341 | console.log('from=', gasTankClientAddress); 342 | 343 | 344 | // inject purchase TX 345 | web3.eth.sendRawTransaction(req.body.tx2, function(err, res) { 346 | web3.eth.getBalance(gastankAccount, function(err, balance) { 347 | console.log('ETH balance of gastank (', gastankAccount, ') is', balance.toString(10) / 1e18); 348 | web3.eth.getBalance(gasTankClientAddress, function(err, balance) { 349 | console.log('ETH balance of gasTankClientAddress (', gasTankClientAddress, ') is', balance.toString(10) / 1e18); 350 | console.log('-----++++++-----++++++-----++++++-----++++++-----++++++-----'); 351 | }); 352 | }); 353 | }) 354 | console.log('-----/FILL TX-----'); 355 | 356 | 357 | res.status(200).json({ 358 | msg: 'sent for processing - hang in there...', 359 | txhash: txhash 360 | }); 361 | 362 | 363 | 364 | }); 365 | }); 366 | 367 | 368 | }); 369 | }); 370 | 371 | 372 | 373 | }); 374 | -------------------------------------------------------------------------------- /test/gasStationTrader.js: -------------------------------------------------------------------------------- 1 | var MiniMeTokenFactory = artifacts.require("./MiniMeToken.sol"); 2 | var MiniMeToken = artifacts.require("./MiniMeToken.sol"); 3 | var gasStationTrader = artifacts.require("./gasStationTrader.sol"); 4 | var etherDelta = artifacts.require("./etherdelta.sol"); 5 | // helper libs 6 | var utility = require('../utility.js')(); 7 | const sha256 = require('js-sha256').sha256; 8 | const sha3 = require('web3/lib/utils/sha3.js'); 9 | var keythereum = require('keythereum'); 10 | 11 | 12 | /** 13 | * creata a ramdon keypair 14 | * 15 | * @return {} { description_of_the_return_value } 16 | */ 17 | function mkkeypair() { 18 | var dk = keythereum.create(); 19 | var keyObject = keythereum.dump("none", dk.privateKey, dk.salt, dk.iv); 20 | return ({ 21 | private: ethUtil.addHexPrefix(dk.privateKey.toString('hex')), 22 | public: ethUtil.addHexPrefix(keyObject.address) 23 | }); 24 | } 25 | 26 | var self = this; 27 | 28 | var availabledeals = []; 29 | 30 | contract('Token Setup', function(accounts) { 31 | 32 | var ETHseller = accounts[3]; 33 | var randomaddress = mkkeypair(); 34 | var gastankInstance = randomaddress.public; 35 | var gastankInstancePrivateKey = randomaddress.private; 36 | 37 | var miniMeTokenFactory; 38 | var swtToken; 39 | // var hashtagRepToken; 40 | var gasStationInstance; 41 | var etherDeltaInstance; 42 | var gasStationTraderInstance; 43 | 44 | // gasstation params 45 | var _triggercost = 2137880000000000; 46 | 47 | var _tokensToExchange = 2137880000000000; 48 | 49 | var self = this; 50 | 51 | describe('Deploy MiniMeToken TokenFactory & SWT token ', function() { 52 | it("should deploy MiniMeToken contract", function(done) { 53 | MiniMeTokenFactory.new().then(function(_miniMeTokenFactory) { 54 | assert.ok(_miniMeTokenFactory.address); 55 | miniMeTokenFactory = _miniMeTokenFactory; 56 | console.log('miniMeTokenFactory created at address', _miniMeTokenFactory.address); 57 | done(); 58 | }); 59 | }); 60 | 61 | it("should deploy a MiniMeToken contract", function(done) { 62 | MiniMeToken.new( 63 | miniMeTokenFactory.address, 64 | 0, 65 | 0, 66 | "Swarm City Token", 67 | 18, 68 | "SWT", 69 | true 70 | ).then(function(_miniMeToken) { 71 | assert.ok(_miniMeToken.address); 72 | console.log('SWT token created at address', _miniMeToken.address); 73 | swtToken = _miniMeToken; 74 | done(); 75 | }); 76 | }); 77 | }); 78 | 79 | describe('Deploy gastaskTrader', function() { 80 | 81 | // it("should deploy a gastaskTrader Instance", function(done) { 82 | // gasStationTrader.new().then(function(_gasStationTrader) { 83 | // assert.ok(_gasStationTrader.address); 84 | // console.log('gasStationTrader created at address', _gasStationTrader.address); 85 | // gasStationTraderInstance = _gasStationTrader; 86 | // done(); 87 | // }); 88 | // }); 89 | 90 | // it("should mint tokens for gastankInstance", function(done) { 91 | // swtToken.generateTokens(gasStationTraderInstance.address, 1 * 1e18).then(function() { 92 | // done(); 93 | // }); 94 | // }); 95 | 96 | // it("should send some ETH to gastankInstance", function(done) { 97 | // self.web3.eth.sendTransaction({ 98 | // from: accounts[0], 99 | // to: gasStationTraderInstance.address, 100 | // value: 1 * 1e18 101 | // }, function() { 102 | // done(); 103 | // }); 104 | // }); 105 | 106 | 107 | it("should mint tokens for gastankInstance", function(done) { 108 | swtToken.generateTokens(gastankInstance, 1 * 1e18).then(function() { 109 | done(); 110 | }); 111 | }); 112 | 113 | it("should send some ETH to gastankInstance", function(done) { 114 | self.web3.eth.sendTransaction({ 115 | from: accounts[0], 116 | to: gastankInstance, 117 | value: 1 * 1e18 118 | }, function() { 119 | done(); 120 | }); 121 | }); 122 | }); 123 | 124 | describe('etherDelta setup', function() { 125 | // function etherDelta(address admin_, address feeAccount_, address accountLevelsAddr_, uint feeMake_, uint feeTake_, uint feeRebate_) { 126 | //admin = admin_; 127 | 128 | it("should deploy etherDelta", function(done) { 129 | etherDelta.new(accounts[0], accounts[0], 0x0, 0, 0, 0, { 130 | gas: 4700000, 131 | }).then(function(instance) { 132 | etherDeltaInstance = instance; 133 | assert.isNotNull(etherDeltaInstance); 134 | done(); 135 | }); 136 | }); 137 | 138 | it("ETHseller should deposit 1 ETH to EtherDelta", function(done) { 139 | 140 | etherDeltaInstance.deposit({ 141 | from: ETHseller, 142 | value: 1 * 1e18 143 | }).then(function(r) { 144 | done(); 145 | }); 146 | }); 147 | 148 | 149 | it("ETHseller should have 1 ETH in the EtherDelta contract", function(done) { 150 | etherDeltaInstance.balanceOf.call(0, ETHseller).then(function(balance) { 151 | assert.equal(balance.valueOf(), 1 * 1e18, "account not correct amount"); 152 | done(); 153 | }); 154 | }); 155 | 156 | it("gastankInstance should give allowance for 1 SWT to EtherDelta", function(done) { 157 | swtToken.approve(etherDeltaInstance.address, 1 * 1e18, { 158 | from: gastankInstance, 159 | }).then(function(r) { 160 | done(); 161 | }); 162 | }); 163 | 164 | it("gastankInstance should deposit 1 SWT to EtherDelta", function(done) { 165 | etherDeltaInstance.depositToken(swtToken.address, 1 * 1e18, { 166 | from: gastankInstance, 167 | }).then(function(r) { 168 | done(); 169 | }); 170 | }); 171 | 172 | it("gastankInstance should have 1 SWT in the EtherDelta contract", function(done) { 173 | etherDeltaInstance.balanceOf.call(swtToken.address, gastankInstance).then(function(balance) { 174 | assert.equal(balance.valueOf(), 1 * 1e18, "account not correct amount"); 175 | done(); 176 | }); 177 | }); 178 | 179 | 180 | 181 | it("ETHseller should place multiple sell orders ETH/SWT etherDelta", function(done) { 182 | 183 | var watcher = etherDeltaInstance.Order(); 184 | watcher.watch(function(error, result) { 185 | console.log('new order event ', result.args); 186 | availabledeals.push(result.args); 187 | watcher.stopWatching(); 188 | done(); 189 | }); 190 | 191 | //function order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce) { 192 | etherDeltaInstance.order( 193 | swtToken.address, // SWT address 194 | 1 * 1e18, // amount of SWT I wanna buy 195 | 0, // 0 = ETH 196 | 1 * 1e18, // Amount of ETH I wanna sell 197 | 10000, // validity 198 | 1, // nonce 199 | { 200 | from: ETHseller 201 | }).then(function(r) { 202 | // this will fire an Order event... 203 | }); 204 | }); 205 | 206 | 207 | 208 | // function availableVolume(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint) { 209 | // bytes32 hash = 210 | 211 | 212 | it("Available volume of order should be 1 * 1e18", function(done) { 213 | 214 | 215 | 216 | const condensed = utility.pack( 217 | [ 218 | etherDeltaInstance.address, //'this.config.contractEtherDeltaAddr, 219 | swtToken.address, //tokenGet, 220 | 1 * 1e18, //amountGet, 221 | 0, //tokenGive, 222 | 1 * 1e18, //amountGive, 223 | 10000, //expires, 224 | 1 // orderNonce, 225 | ], [160, 160, 256, 160, 256, 256, 256]); 226 | const hash = sha256(new Buffer(condensed, 'hex')); 227 | 228 | console.log('hash=', hash); 229 | 230 | utility.sign(ETHseller, hash, gastankInstancePrivateKey, function(err, signature) { 231 | if (err) { 232 | console.log('ERR', err); 233 | return done(); 234 | } 235 | console.log('sig=', signature); 236 | 237 | etherDeltaInstance.availableVolume.call( 238 | swtToken.address, 239 | 1 * 1e18, 240 | 0, 241 | 1 * 1e18, 242 | 10000, 243 | 1, 244 | ETHseller, 245 | signature.v, 246 | signature.r, 247 | signature.s 248 | 249 | ).then(function(volume) { 250 | assert.equal(volume.valueOf(), 1 * 1e18, "volume not correct amount"); 251 | done(); 252 | }); 253 | }); 254 | }); 255 | 256 | 257 | 258 | // function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount) { 259 | 260 | 261 | 262 | describe('make & do atomic trade', function() { 263 | // function etherDelta(address admin_, address feeAccount_, address accountLevelsAddr_, uint feeMake_, uint feeTake_, uint feeRebate_) { 264 | //admin = admin_; 265 | 266 | it("should find the best deal ATM", function(done) { 267 | // TODO determine criteria for this 268 | bestDeal = availabledeals[0]; 269 | done(); 270 | }); 271 | 272 | it("should create deal", function(done) { 273 | 274 | }); 275 | }); 276 | 277 | 278 | // it("should buy ETH for SWT", function(done) { 279 | 280 | // // function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount) { 281 | 282 | // const condensed = utility.pack( 283 | // [ 284 | // etherDeltaInstance.address, //'this.config.contractEtherDeltaAddr, 285 | // swtToken.address, //tokenGet, 286 | // 1 * 1e18, //amountGet, 287 | // 0, //tokenGive, 288 | // 1 * 1e18, //amountGive, 289 | // 10000, //expires, 290 | // 1 // orderNonce, 291 | // ], [160, 160, 256, 160, 256, 256, 256]); 292 | // const hash = sha256(new Buffer(condensed, 'hex')); 293 | 294 | // console.log('hash=', hash); 295 | 296 | // utility.sign(gastankInstance, hash, gastankInstancePrivateKey, function(err, signature) { 297 | // if (err) { 298 | // console.log('ERR', err); 299 | // return done(); 300 | // } 301 | // console.log('sig=', signature); 302 | 303 | // function prefixMessage(msgIn) { 304 | // let msg = msgIn; 305 | // msg = new Buffer(msg.slice(2), 'hex'); 306 | // msg = Buffer.concat([ 307 | // new Buffer(`\x19Ethereum Signed Message:\n${msg.length.toString()}`), 308 | // msg 309 | // ]); 310 | // console.log('MSG TO BE HASHED 2', msg.toString('hex')); 311 | // msg = sha3(`0x${msg.toString('hex')}`, { 312 | // encoding: 'hex' 313 | // }); 314 | // console.log('HASH 2', msg.toString('hex')); 315 | // msg = new Buffer((msg.substring(0, 2) === '0x') ? msg.slice(2) : msg, 'hex'); 316 | // return `0x${msg.toString('hex')}`; 317 | // } 318 | 319 | // utility.verify(gastankInstance, signature.v, signature.r, signature.s, prefixMessage('0x' + hash), function(err, res) { 320 | // if (err) { 321 | // console.log('verify err', err); 322 | // } 323 | // console.log('verify result=', res); 324 | 325 | // etherDeltaInstance.trade( 326 | // swtToken.address, 327 | // 1 * 1e18, 328 | // 0, 329 | // 1 * 1e18, 330 | // 10000, 331 | // 1, 332 | // ETHseller, 333 | // signature.v, 334 | // signature.r, 335 | // signature.s, 1 * 1e10, { 336 | // from: gastankInstance 337 | // }).then(function(r) { 338 | // done(); 339 | // }); 340 | // }); 341 | 342 | // }); 343 | // }); 344 | 345 | 346 | // it("ETHseller should now have 1 SWT in the EtherDelta contract", function(done) { 347 | // etherDeltaInstance.balanceOf.call(swtToken.address, ETHseller).then(function(balance) { 348 | // assert.equal(balance.valueOf(), 1 * 1e10, "account not correct amount"); 349 | // done(); 350 | // }); 351 | // }); 352 | 353 | }); 354 | }); 355 | -------------------------------------------------------------------------------- /contracts/etherdelta.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.9; 2 | 3 | contract SafeMath { 4 | function safeMul(uint a, uint b) internal returns (uint) { 5 | uint c = a * b; 6 | assert(a == 0 || c / a == b); 7 | return c; 8 | } 9 | 10 | function safeSub(uint a, uint b) internal returns (uint) { 11 | assert(b <= a); 12 | return a - b; 13 | } 14 | 15 | function safeAdd(uint a, uint b) internal returns (uint) { 16 | uint c = a + b; 17 | assert(c>=a && c>=b); 18 | return c; 19 | } 20 | 21 | function assert(bool assertion) internal { 22 | if (!assertion) throw; 23 | } 24 | } 25 | 26 | contract Token { 27 | /// @return total amount of tokens 28 | function totalSupply() constant returns (uint256 supply) {} 29 | 30 | /// @param _owner The address from which the balance will be retrieved 31 | /// @return The balance 32 | function balanceOf(address _owner) constant returns (uint256 balance) {} 33 | 34 | /// @notice send `_value` token to `_to` from `msg.sender` 35 | /// @param _to The address of the recipient 36 | /// @param _value The amount of token to be transferred 37 | /// @return Whether the transfer was successful or not 38 | function transfer(address _to, uint256 _value) returns (bool success) {} 39 | 40 | /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` 41 | /// @param _from The address of the sender 42 | /// @param _to The address of the recipient 43 | /// @param _value The amount of token to be transferred 44 | /// @return Whether the transfer was successful or not 45 | function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {} 46 | 47 | /// @notice `msg.sender` approves `_addr` to spend `_value` tokens 48 | /// @param _spender The address of the account able to transfer the tokens 49 | /// @param _value The amount of wei to be approved for transfer 50 | /// @return Whether the approval was successful or not 51 | function approve(address _spender, uint256 _value) returns (bool success) {} 52 | 53 | /// @param _owner The address of the account owning tokens 54 | /// @param _spender The address of the account able to transfer the tokens 55 | /// @return Amount of remaining tokens allowed to spent 56 | function allowance(address _owner, address _spender) constant returns (uint256 remaining) {} 57 | 58 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 59 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 60 | 61 | uint public decimals; 62 | string public name; 63 | } 64 | 65 | contract StandardToken is Token { 66 | 67 | function transfer(address _to, uint256 _value) returns (bool success) { 68 | //Default assumes totalSupply can't be over max (2^256 - 1). 69 | //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. 70 | //Replace the if with this one instead. 71 | if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) { 72 | //if (balances[msg.sender] >= _value && _value > 0) { 73 | balances[msg.sender] -= _value; 74 | balances[_to] += _value; 75 | Transfer(msg.sender, _to, _value); 76 | return true; 77 | } else { return false; } 78 | } 79 | 80 | function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { 81 | //same as above. Replace this line with the following if you want to protect against wrapping uints. 82 | if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) { 83 | //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) { 84 | balances[_to] += _value; 85 | balances[_from] -= _value; 86 | allowed[_from][msg.sender] -= _value; 87 | Transfer(_from, _to, _value); 88 | return true; 89 | } else { return false; } 90 | } 91 | 92 | function balanceOf(address _owner) constant returns (uint256 balance) { 93 | return balances[_owner]; 94 | } 95 | 96 | function approve(address _spender, uint256 _value) returns (bool success) { 97 | allowed[msg.sender][_spender] = _value; 98 | Approval(msg.sender, _spender, _value); 99 | return true; 100 | } 101 | 102 | function allowance(address _owner, address _spender) constant returns (uint256 remaining) { 103 | return allowed[_owner][_spender]; 104 | } 105 | 106 | mapping(address => uint256) balances; 107 | 108 | mapping (address => mapping (address => uint256)) allowed; 109 | 110 | uint256 public totalSupply; 111 | } 112 | 113 | contract ReserveToken is StandardToken, SafeMath { 114 | address public minter; 115 | function ReserveToken() { 116 | minter = msg.sender; 117 | } 118 | function create(address account, uint amount) { 119 | if (msg.sender != minter) throw; 120 | balances[account] = safeAdd(balances[account], amount); 121 | totalSupply = safeAdd(totalSupply, amount); 122 | } 123 | function destroy(address account, uint amount) { 124 | if (msg.sender != minter) throw; 125 | if (balances[account] < amount) throw; 126 | balances[account] = safeSub(balances[account], amount); 127 | totalSupply = safeSub(totalSupply, amount); 128 | } 129 | } 130 | 131 | contract AccountLevels { 132 | //given a user, returns an account level 133 | //0 = regular user (pays take fee and make fee) 134 | //1 = market maker silver (pays take fee, no make fee, gets rebate) 135 | //2 = market maker gold (pays take fee, no make fee, gets entire counterparty's take fee as rebate) 136 | function accountLevel(address user) constant returns(uint) {} 137 | } 138 | 139 | contract AccountLevelsTest is AccountLevels { 140 | mapping (address => uint) public accountLevels; 141 | 142 | function setAccountLevel(address user, uint level) { 143 | accountLevels[user] = level; 144 | } 145 | 146 | function accountLevel(address user) constant returns(uint) { 147 | return accountLevels[user]; 148 | } 149 | } 150 | 151 | contract EtherDelta is SafeMath { 152 | address public admin; //the admin address 153 | address public feeAccount; //the account that will receive fees 154 | address public accountLevelsAddr; //the address of the AccountLevels contract 155 | uint public feeMake; //percentage times (1 ether) 156 | uint public feeTake; //percentage times (1 ether) 157 | uint public feeRebate; //percentage times (1 ether) 158 | mapping (address => mapping (address => uint)) public tokens; //mapping of token addresses to mapping of account balances (token=0 means Ether) 159 | mapping (address => mapping (bytes32 => bool)) public orders; //mapping of user accounts to mapping of order hashes to booleans (true = submitted by user, equivalent to offchain signature) 160 | mapping (address => mapping (bytes32 => uint)) public orderFills; //mapping of user accounts to mapping of order hashes to uints (amount of order that has been filled) 161 | 162 | event Order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user); 163 | event Cancel(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s); 164 | event Trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, address get, address give); 165 | event Deposit(address token, address user, uint amount, uint balance); 166 | event Withdraw(address token, address user, uint amount, uint balance); 167 | 168 | function EtherDelta(address admin_, address feeAccount_, address accountLevelsAddr_, uint feeMake_, uint feeTake_, uint feeRebate_) { 169 | admin = admin_; 170 | feeAccount = feeAccount_; 171 | accountLevelsAddr = accountLevelsAddr_; 172 | feeMake = feeMake_; 173 | feeTake = feeTake_; 174 | feeRebate = feeRebate_; 175 | } 176 | 177 | function() { 178 | throw; 179 | } 180 | 181 | function changeAdmin(address admin_) { 182 | if (msg.sender != admin) throw; 183 | admin = admin_; 184 | } 185 | 186 | function changeAccountLevelsAddr(address accountLevelsAddr_) { 187 | if (msg.sender != admin) throw; 188 | accountLevelsAddr = accountLevelsAddr_; 189 | } 190 | 191 | function changeFeeAccount(address feeAccount_) { 192 | if (msg.sender != admin) throw; 193 | feeAccount = feeAccount_; 194 | } 195 | 196 | function changeFeeMake(uint feeMake_) { 197 | if (msg.sender != admin) throw; 198 | if (feeMake_ > feeMake) throw; 199 | feeMake = feeMake_; 200 | } 201 | 202 | function changeFeeTake(uint feeTake_) { 203 | if (msg.sender != admin) throw; 204 | if (feeTake_ > feeTake || feeTake_ < feeRebate) throw; 205 | feeTake = feeTake_; 206 | } 207 | 208 | function changeFeeRebate(uint feeRebate_) { 209 | if (msg.sender != admin) throw; 210 | if (feeRebate_ < feeRebate || feeRebate_ > feeTake) throw; 211 | feeRebate = feeRebate_; 212 | } 213 | 214 | function deposit() payable { 215 | tokens[0][msg.sender] = safeAdd(tokens[0][msg.sender], msg.value); 216 | Deposit(0, msg.sender, msg.value, tokens[0][msg.sender]); 217 | } 218 | 219 | function withdraw(uint amount) { 220 | if (tokens[0][msg.sender] < amount) throw; 221 | tokens[0][msg.sender] = safeSub(tokens[0][msg.sender], amount); 222 | if (!msg.sender.call.value(amount)()) throw; 223 | Withdraw(0, msg.sender, amount, tokens[0][msg.sender]); 224 | } 225 | 226 | function depositToken(address token, uint amount) { 227 | //remember to call Token(address).approve(this, amount) or this contract will not be able to do the transfer on your behalf. 228 | if (token==0) throw; 229 | if (!Token(token).transferFrom(msg.sender, this, amount)) throw; 230 | tokens[token][msg.sender] = safeAdd(tokens[token][msg.sender], amount); 231 | Deposit(token, msg.sender, amount, tokens[token][msg.sender]); 232 | } 233 | 234 | function withdrawToken(address token, uint amount) { 235 | if (token==0) throw; 236 | if (tokens[token][msg.sender] < amount) throw; 237 | tokens[token][msg.sender] = safeSub(tokens[token][msg.sender], amount); 238 | if (!Token(token).transfer(msg.sender, amount)) throw; 239 | Withdraw(token, msg.sender, amount, tokens[token][msg.sender]); 240 | } 241 | 242 | function balanceOf(address token, address user) constant returns (uint) { 243 | return tokens[token][user]; 244 | } 245 | 246 | function order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce) { 247 | bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); 248 | orders[msg.sender][hash] = true; 249 | Order(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender); 250 | } 251 | 252 | function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount) { 253 | //amount is in amountGet terms 254 | bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); 255 | if (!( 256 | (orders[user][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == user) && 257 | block.number <= expires && 258 | safeAdd(orderFills[user][hash], amount) <= amountGet 259 | )) throw; 260 | tradeBalances(tokenGet, amountGet, tokenGive, amountGive, user, amount); 261 | orderFills[user][hash] = safeAdd(orderFills[user][hash], amount); 262 | Trade(tokenGet, amount, tokenGive, amountGive * amount / amountGet, user, msg.sender); 263 | } 264 | 265 | function tradeBalances(address tokenGet, uint amountGet, address tokenGive, uint amountGive, address user, uint amount) private { 266 | uint feeMakeXfer = safeMul(amount, feeMake) / (1 ether); 267 | uint feeTakeXfer = safeMul(amount, feeTake) / (1 ether); 268 | uint feeRebateXfer = 0; 269 | if (accountLevelsAddr != 0x0) { 270 | uint accountLevel = AccountLevels(accountLevelsAddr).accountLevel(user); 271 | if (accountLevel==1) feeRebateXfer = safeMul(amount, feeRebate) / (1 ether); 272 | if (accountLevel==2) feeRebateXfer = feeTakeXfer; 273 | } 274 | tokens[tokenGet][msg.sender] = safeSub(tokens[tokenGet][msg.sender], safeAdd(amount, feeTakeXfer)); 275 | tokens[tokenGet][user] = safeAdd(tokens[tokenGet][user], safeSub(safeAdd(amount, feeRebateXfer), feeMakeXfer)); 276 | tokens[tokenGet][feeAccount] = safeAdd(tokens[tokenGet][feeAccount], safeSub(safeAdd(feeMakeXfer, feeTakeXfer), feeRebateXfer)); 277 | tokens[tokenGive][user] = safeSub(tokens[tokenGive][user], safeMul(amountGive, amount) / amountGet); 278 | tokens[tokenGive][msg.sender] = safeAdd(tokens[tokenGive][msg.sender], safeMul(amountGive, amount) / amountGet); 279 | } 280 | 281 | function testTrade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount, address sender) constant returns(bool) { 282 | if (!( 283 | tokens[tokenGet][sender] >= amount && 284 | availableVolume(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, user, v, r, s) >= amount 285 | )) return false; 286 | return true; 287 | } 288 | 289 | function availableVolume(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint) { 290 | bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); 291 | if (!( 292 | (orders[user][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == user) && 293 | block.number <= expires 294 | )) return 0; 295 | uint available1 = safeSub(amountGet, orderFills[user][hash]); 296 | uint available2 = safeMul(tokens[tokenGive][user], amountGet) / amountGive; 297 | if (available1 0); 145 | done(); 146 | }); 147 | }); 148 | 149 | it("gasstation-contract should have ETH", function(done) { 150 | self.web3.eth.getBalance(gasStationInstance.address, function(err, ethbalance) { 151 | console.log('gasstation-contract owns', ethbalance.toNumber(10), 'Wei'); 152 | assert.ok(ethbalance.toNumber(10) > 0); 153 | done(); 154 | }); 155 | }); 156 | 157 | it("gasstation-client should have a Token balance ", function(done) { 158 | swtToken.balanceOf.call(randomkeys[gasstation_client].public).then(function(balance) { 159 | _swtbalance = balance.toNumber(10); 160 | console.log('gasstation-client token balance =', _swtbalance); 161 | assert.ok(_swtbalance > 0); 162 | done(); 163 | }); 164 | }); 165 | 166 | it("gasstation-client should have no ETH", function(done) { 167 | self.web3.eth.getBalance(randomkeys[gasstation_client].public, function(err, ethbalance) { 168 | console.log('gasstation-client owns', ethbalance.toNumber(10), 'Wei'); 169 | assert.ok(ethbalance.toNumber(10) == 0); 170 | done(); 171 | }); 172 | }); 173 | 174 | it("should print instructions", function(done) { 175 | console.log('----------------------------------------------------'); 176 | console.log('-----------------STEP 1-----------------------------'); 177 | console.log('put in frontend/index.html -> gs-client attribute'); 178 | console.log('erc20="' + swtToken.address + '"'); 179 | console.log('-----------------STEP 2-----------------------------'); 180 | console.log('put in file .env'); 181 | console.log('gastankaddress=\'' + gasStationInstance.address + '\''); 182 | console.log('erc20token=\'' + swtToken.address + '\''); 183 | console.log('----------------------------------------------------'); 184 | done(); 185 | 186 | }); 187 | }); 188 | 189 | describe('test transaction on gasstation', function() { 190 | 191 | var approvaltx; 192 | 193 | // create some ramdon parameters for the transaction we'd like to do 194 | var random = Math.floor(Math.random() * 1e6); 195 | var valid_until = self.web3.eth.blockNumber + 10; 196 | var amount_take = gasPrice * 5000000; 197 | var amount_give = 5000000 * 5; 198 | 199 | 200 | it("should create getapproval TX", function(done) { 201 | var tx = utility.getapprovaltx(self.web3, randomkeys[gasstation_client].public, randomkeys[gasstation_client].private, swtToken.address, amount_give, gasStationInstance.address, gasPrice, function(err, tx) { 202 | console.log('err=', err, 'tx=', tx); 203 | 204 | approvaltx = tx; 205 | 206 | //var decodedTx = new ethTx(tx.signedtx); 207 | console.log('approvaltx cost =', tx.cost); 208 | 209 | done(); 210 | 211 | }); 212 | }); 213 | 214 | it("balances should be sufficient", function(done) { 215 | 216 | swtToken.balanceOf.call(randomkeys[gasstation_client].public).then(function(swtbalance) { 217 | self.web3.eth.getBalance(gasStationInstance.address, function(err, ethbalance) { 218 | console.log('gasstation_client want to take (in ETH units) : ', amount_take); 219 | console.log('gasstation contract has (in ETH units) =', ethbalance.toNumber(10)); 220 | console.log('gasstation_client wants to give ( in SWT )', amount_give); 221 | console.log('gasstation_client has ( in SWT )', swtbalance.toNumber(10)); 222 | 223 | assert.isAbove(ethbalance.toNumber(10), amount_take - approvaltx.cost); 224 | assert.isAbove(swtbalance.toNumber(10), amount_give); 225 | 226 | done(); 227 | }); 228 | }); 229 | }); 230 | 231 | it("gasstation-client should have no ETH", function(done) { 232 | self.web3.eth.getBalance(randomkeys[gasstation_client].public, function(err, ethbalance) { 233 | console.log('gasstation-client owns', ethbalance.toNumber(10), 'Wei'); 234 | assert.equal(ethbalance.toNumber(10), 0); 235 | done(); 236 | }); 237 | }); 238 | 239 | it("should send gas to cover cost", function(done) { 240 | self.web3.eth.sendTransaction({ 241 | from: accounts[0], 242 | to: randomkeys[gasstation_client].public, 243 | value: approvaltx.cost 244 | }, function(err) { 245 | done(); 246 | }) 247 | }); 248 | 249 | it("gasstation-client should have enough ETH to cover initial tx costs", function(done) { 250 | self.web3.eth.getBalance(randomkeys[gasstation_client].public, function(err, ethbalance) { 251 | console.log('gasstation-client owns', ethbalance.toNumber(10), 'Wei'); 252 | assert.equal(ethbalance.toNumber(10), approvaltx.cost); 253 | done(); 254 | }); 255 | }); 256 | 257 | it("gasstation-client should have zero allowance", function(done) { 258 | var allowance_from = randomkeys[gasstation_client].public; 259 | var allowance_to = gasStationInstance.address; 260 | console.log('check allowance -> from=', allowance_from, 'to=', allowance_to); 261 | swtToken.allowance.call(allowance_from, allowance_to).then(function(allowance) { 262 | console.log('allowance=', allowance.toNumber(10)); 263 | assert.equal(allowance, 0); 264 | done(); 265 | }); 266 | }); 267 | 268 | it("gasstation-client should be able to execute allowance TX", function(done) { 269 | web3.eth.sendRawTransaction(approvaltx.signedtx, function(err, res) { 270 | console.log('create allowance - tx sent', err, res); 271 | done(); 272 | }); 273 | 274 | }); 275 | 276 | it("gasstation-client should have an allowance", function(done) { 277 | var allowance_from = randomkeys[gasstation_client].public; 278 | var allowance_to = gasStationInstance.address; 279 | console.log('check allowance -> from=', allowance_from, 'to=', allowance_to); 280 | swtToken.allowance.call(allowance_from, allowance_to).then(function(allowance) { 281 | console.log('allowance=', allowance.toNumber(10)); 282 | assert.equal(allowance, amount_give); 283 | done(); 284 | }); 285 | }); 286 | 287 | 288 | // it("gasstation-client should have no ETH after tx", function(done) { 289 | // self.web3.eth.getBalance(randomkeys[gasstation_client].public, function(err, ethbalance) { 290 | // console.log('gasstation-client owns', ethbalance.toNumber(10), 'Wei'); 291 | // assert.equal(ethbalance.toNumber(10), 0); 292 | // done(); 293 | // }); 294 | // }); 295 | 296 | it("gasstation_withdrawaccount should have a zero Token balance ", function(done) { 297 | swtToken.balanceOf.call(randomkeys[gasstation_withdrawaccount].public).then(function(balance) { 298 | _swtbalance = balance.toNumber(10); 299 | console.log('gasstation_withdrawaccount token balance =', _swtbalance); 300 | assert.ok(_swtbalance == 0); 301 | done(); 302 | }); 303 | }); 304 | 305 | 306 | it("gasstation-server should be able to exchange gas", function(done) { 307 | 308 | var sig = utility.signgastankparameters(swtToken.address, gasStationInstance.address, randomkeys[gasstation_client].public, amount_take - approvaltx.cost, amount_give, valid_until, random, randomkeys[gasstation_client].private) 309 | console.log('sig =>', sig, 'rand=', random, 'valid_until=', valid_until); 310 | 311 | gasStationInstance.pushFill(swtToken.address, valid_until, random, amount_take - approvaltx.cost, amount_give, randomkeys[gasstation_client].public, sig.v, sig.r, sig.s, { 312 | from: accounts[2] 313 | }).then(function(txhash) { 314 | console.log('pushFill', txhash); 315 | console.log('pushFill', txhash.logs[0].args); 316 | done(); 317 | }).catch(function(e) { 318 | assert.fail(null, null, 'this function should not throw', e); 319 | done(); 320 | }); 321 | 322 | }); 323 | 324 | it("gasstation-server should be unable to exchange with same parameters twice ", function(done) { 325 | 326 | var sig = utility.signgastankparameters(swtToken.address, gasStationInstance.address, randomkeys[gasstation_client].public, amount_take - approvaltx.cost, amount_give, valid_until, random, randomkeys[gasstation_client].private) 327 | console.log('sig =>', sig, 'rand=', random, 'valid_until=', valid_until); 328 | 329 | gasStationInstance.pushFill(swtToken.address, valid_until, random, amount_take - approvaltx.cost, amount_give, randomkeys[gasstation_client].public, sig.v, sig.r, sig.s, { 330 | from: accounts[2] 331 | }).then(function(txhash) { 332 | assert.fail(null, null, 'this function should throw', e); 333 | done(); 334 | }).catch(function(e) { 335 | 336 | done(); 337 | }); 338 | 339 | }); 340 | it("gasstation_withdrawaccount should have a Token balance ", function(done) { 341 | swtToken.balanceOf.call(randomkeys[gasstation_withdrawaccount].public).then(function(balance) { 342 | _swtbalance = balance.toNumber(10); 343 | console.log('gasstation_withdrawaccount token balance =', _swtbalance); 344 | assert.ok(_swtbalance > 0); 345 | done(); 346 | }); 347 | }); 348 | 349 | 350 | 351 | }); 352 | 353 | describe('clean up gasstation', function() { 354 | 355 | // it("gasstation-contract should have a non-zero Token balance ", function(done) { 356 | // swtToken.balanceOf.call(gasStationInstance.address).then(function(balance) { 357 | // _swtbalance = balance.toNumber(10); 358 | // console.log('gasstation-contract token balance =', _swtbalance); 359 | // assert.ok(_swtbalance > 0); 360 | // done(); 361 | // }); 362 | // }); 363 | 364 | // it("withDrawtokens should not be possible from other account than the owner", function(done) { 365 | // gasStationInstance.withdrawTokens(swtToken.address, accounts[4], { 366 | // from: accounts[2] 367 | // }).then(function() { 368 | // done(); 369 | // // this should fail 370 | // }).catch(function(e) { 371 | // assert.fail(null, null, 'this function should not throw', e); 372 | // done(); 373 | // }); 374 | // }); 375 | 376 | // it("gasstation-contract should have a zero Token balance ", function(done) { 377 | // swtToken.balanceOf.call(gasStationInstance.address).then(function(balance) { 378 | // _swtbalance = balance.toNumber(10); 379 | // console.log('gasstation-contract token balance =', _swtbalance); 380 | // assert.equal(_swtbalance, 0); 381 | // done(); 382 | // }); 383 | // }); 384 | 385 | 386 | // describe('Transfer ownership of gasstation', function() { 387 | 388 | // it("gasstation-contract should get transfered ", function(done) { 389 | // gasStationInstance.transferOwnership(randomkeys[gasstation_nodeserver].public, { 390 | // from: accounts[2] 391 | // }).then(function() { 392 | // gasStationInstance.owner.call().then(function(owner) { 393 | // console.log('owner of gasstation is now', owner); 394 | // assert.equal(owner, randomkeys[gasstation_nodeserver].public); 395 | // done(); 396 | // }); 397 | // }); 398 | // }); 399 | 400 | // }); 401 | 402 | }); 403 | }); 404 | -------------------------------------------------------------------------------- /contracts/MiniMeToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.6; 2 | 3 | /* 4 | Copyright 2016, Jordi Baylina 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | /// @title MiniMeToken Contract 21 | /// @author Jordi Baylina 22 | /// @dev This token contract's goal is to make it easy for anyone to clone this 23 | /// token using the token distribution at a given block, this will allow DAO's 24 | /// and DApps to upgrade their features in a decentralized manner without 25 | /// affecting the original token 26 | /// @dev It is ERC20 compliant, but still needs to under go further testing. 27 | 28 | 29 | /// @dev The token controller contract must implement these functions 30 | contract TokenController { 31 | /// @notice Called when `_owner` sends ether to the MiniMe Token contract 32 | /// @param _owner The address that sent the ether to create tokens 33 | /// @return True if the ether is accepted, false if it throws 34 | function proxyPayment(address _owner) payable returns(bool); 35 | 36 | /// @notice Notifies the controller about a token transfer allowing the 37 | /// controller to react if desired 38 | /// @param _from The origin of the transfer 39 | /// @param _to The destination of the transfer 40 | /// @param _amount The amount of the transfer 41 | /// @return False if the controller does not authorize the transfer 42 | function onTransfer(address _from, address _to, uint _amount) returns(bool); 43 | 44 | /// @notice Notifies the controller about an approval allowing the 45 | /// controller to react if desired 46 | /// @param _owner The address that calls `approve()` 47 | /// @param _spender The spender in the `approve()` call 48 | /// @param _amount The amount in the `approve()` call 49 | /// @return False if the controller does not authorize the approval 50 | function onApprove(address _owner, address _spender, uint _amount) 51 | returns(bool); 52 | } 53 | 54 | contract Controlled { 55 | /// @notice The address of the controller is the only address that can call 56 | /// a function with this modifier 57 | modifier onlyController { if (msg.sender != controller) throw; _; } 58 | 59 | address public controller; 60 | 61 | function Controlled() { controller = msg.sender;} 62 | 63 | /// @notice Changes the controller of the contract 64 | /// @param _newController The new controller of the contract 65 | function changeController(address _newController) onlyController { 66 | controller = _newController; 67 | } 68 | } 69 | 70 | contract ApproveAndCallFallBack { 71 | function receiveApproval(address from, uint256 _amount, address _token, bytes _data); 72 | } 73 | 74 | /// @dev The actual token contract, the default controller is the msg.sender 75 | /// that deploys the contract, so usually this token will be deployed by a 76 | /// token controller contract, which Giveth will call a "Campaign" 77 | contract MiniMeToken is Controlled { 78 | 79 | string public name; //The Token's name: e.g. DigixDAO Tokens 80 | uint8 public decimals; //Number of decimals of the smallest unit 81 | string public symbol; //An identifier: e.g. REP 82 | string public version = 'MMT_0.1'; //An arbitrary versioning scheme 83 | 84 | 85 | /// @dev `Checkpoint` is the structure that attaches a block number to a 86 | /// given value, the block number attached is the one that last changed the 87 | /// value 88 | struct Checkpoint { 89 | 90 | // `fromBlock` is the block number that the value was generated from 91 | uint128 fromBlock; 92 | 93 | // `value` is the amount of tokens at a specific block number 94 | uint128 value; 95 | } 96 | 97 | // `parentToken` is the Token address that was cloned to produce this token; 98 | // it will be 0x0 for a token that was not cloned 99 | MiniMeToken public parentToken; 100 | 101 | // `parentSnapShotBlock` is the block number from the Parent Token that was 102 | // used to determine the initial distribution of the Clone Token 103 | uint public parentSnapShotBlock; 104 | 105 | // `creationBlock` is the block number that the Clone Token was created 106 | uint public creationBlock; 107 | 108 | // `balances` is the map that tracks the balance of each address, in this 109 | // contract when the balance changes the block number that the change 110 | // occurred is also included in the map 111 | mapping (address => Checkpoint[]) balances; 112 | 113 | // `allowed` tracks any extra transfer rights as in all ERC20 tokens 114 | mapping (address => mapping (address => uint256)) allowed; 115 | 116 | // Tracks the history of the `totalSupply` of the token 117 | Checkpoint[] totalSupplyHistory; 118 | 119 | // Flag that determines if the token is transferable or not. 120 | bool public transfersEnabled; 121 | 122 | // The factory used to create new clone tokens 123 | MiniMeTokenFactory public tokenFactory; 124 | 125 | //////////////// 126 | // Constructor 127 | //////////////// 128 | 129 | /// @notice Constructor to create a MiniMeToken 130 | /// @param _tokenFactory The address of the MiniMeTokenFactory contract that 131 | /// will create the Clone token contracts, the token factory needs to be 132 | /// deployed first 133 | /// @param _parentToken Address of the parent token, set to 0x0 if it is a 134 | /// new token 135 | /// @param _parentSnapShotBlock Block of the parent token that will 136 | /// determine the initial distribution of the clone token, set to 0 if it 137 | /// is a new token 138 | /// @param _tokenName Name of the new token 139 | /// @param _decimalUnits Number of decimals of the new token 140 | /// @param _tokenSymbol Token Symbol for the new token 141 | /// @param _transfersEnabled If true, tokens will be able to be transferred 142 | function MiniMeToken( 143 | address _tokenFactory, 144 | address _parentToken, 145 | uint _parentSnapShotBlock, 146 | string _tokenName, 147 | uint8 _decimalUnits, 148 | string _tokenSymbol, 149 | bool _transfersEnabled 150 | ) { 151 | tokenFactory = MiniMeTokenFactory(_tokenFactory); 152 | name = _tokenName; // Set the name 153 | decimals = _decimalUnits; // Set the decimals 154 | symbol = _tokenSymbol; // Set the symbol 155 | parentToken = MiniMeToken(_parentToken); 156 | parentSnapShotBlock = _parentSnapShotBlock; 157 | transfersEnabled = _transfersEnabled; 158 | creationBlock = block.number; 159 | } 160 | 161 | 162 | /////////////////// 163 | // ERC20 Methods 164 | /////////////////// 165 | 166 | /// @notice Send `_amount` tokens to `_to` from `msg.sender` 167 | /// @param _to The address of the recipient 168 | /// @param _amount The amount of tokens to be transferred 169 | /// @return Whether the transfer was successful or not 170 | function transfer(address _to, uint256 _amount) returns (bool success) { 171 | if (!transfersEnabled) throw; 172 | return doTransfer(msg.sender, _to, _amount); 173 | } 174 | 175 | /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it 176 | /// is approved by `_from` 177 | /// @param _from The address holding the tokens being transferred 178 | /// @param _to The address of the recipient 179 | /// @param _amount The amount of tokens to be transferred 180 | /// @return True if the transfer was successful 181 | function transferFrom(address _from, address _to, uint256 _amount 182 | ) returns (bool success) { 183 | 184 | // The controller of this contract can move tokens around at will, 185 | // this is important to recognize! Confirm that you trust the 186 | // controller of this contract, which in most situations should be 187 | // another open source smart contract or 0x0 188 | if (msg.sender != controller) { 189 | if (!transfersEnabled) throw; 190 | 191 | // The standard ERC 20 transferFrom functionality 192 | if (allowed[_from][msg.sender] < _amount) return false; 193 | allowed[_from][msg.sender] -= _amount; 194 | } 195 | return doTransfer(_from, _to, _amount); 196 | } 197 | 198 | /// @dev This is the actual transfer function in the token contract, it can 199 | /// only be called by other functions in this contract. 200 | /// @param _from The address holding the tokens being transferred 201 | /// @param _to The address of the recipient 202 | /// @param _amount The amount of tokens to be transferred 203 | /// @return True if the transfer was successful 204 | function doTransfer(address _from, address _to, uint _amount 205 | ) internal returns(bool) { 206 | 207 | if (_amount == 0) { 208 | return true; 209 | } 210 | 211 | // Do not allow transfer to 0x0 or the token contract itself 212 | if ((_to == 0) || (_to == address(this))) throw; 213 | 214 | // If the amount being transfered is more than the balance of the 215 | // account the transfer returns false 216 | var previousBalanceFrom = balanceOfAt(_from, block.number); 217 | if (previousBalanceFrom < _amount) { 218 | return false; 219 | } 220 | 221 | // Alerts the token controller of the transfer 222 | if (isContract(controller)) { 223 | if (!TokenController(controller).onTransfer(_from, _to, _amount)) 224 | throw; 225 | } 226 | 227 | // First update the balance array with the new value for the address 228 | // sending the tokens 229 | updateValueAtNow(balances[_from], previousBalanceFrom - _amount); 230 | 231 | // Then update the balance array with the new value for the address 232 | // receiving the tokens 233 | var previousBalanceTo = balanceOfAt(_to, block.number); 234 | if (previousBalanceTo + _amount < previousBalanceTo) throw; // Check for overflow 235 | updateValueAtNow(balances[_to], previousBalanceTo + _amount); 236 | 237 | // An event to make the transfer easy to find on the blockchain 238 | Transfer(_from, _to, _amount); 239 | 240 | return true; 241 | } 242 | 243 | /// @param _owner The address that's balance is being requested 244 | /// @return The balance of `_owner` at the current block 245 | function balanceOf(address _owner) constant returns (uint256 balance) { 246 | return balanceOfAt(_owner, block.number); 247 | } 248 | 249 | /// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on 250 | /// its behalf. This is a modified version of the ERC20 approve function 251 | /// to be a little bit safer 252 | /// @param _spender The address of the account able to transfer the tokens 253 | /// @param _amount The amount of tokens to be approved for transfer 254 | /// @return True if the approval was successful 255 | function approve(address _spender, uint256 _amount) returns (bool success) { 256 | if (!transfersEnabled) throw; 257 | 258 | // To change the approve amount you first have to reduce the addresses` 259 | // allowance to zero by calling `approve(_spender,0)` if it is not 260 | // already 0 to mitigate the race condition described here: 261 | // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 262 | if ((_amount!=0) && (allowed[msg.sender][_spender] !=0)) throw; 263 | 264 | // Alerts the token controller of the approve function call 265 | if (isContract(controller)) { 266 | if (!TokenController(controller).onApprove(msg.sender, _spender, _amount)) 267 | throw; 268 | } 269 | 270 | allowed[msg.sender][_spender] = _amount; 271 | Approval(msg.sender, _spender, _amount); 272 | return true; 273 | } 274 | 275 | /// @dev This function makes it easy to read the `allowed[]` map 276 | /// @param _owner The address of the account that owns the token 277 | /// @param _spender The address of the account able to transfer the tokens 278 | /// @return Amount of remaining tokens of _owner that _spender is allowed 279 | /// to spend 280 | function allowance(address _owner, address _spender 281 | ) constant returns (uint256 remaining) { 282 | return allowed[_owner][_spender]; 283 | } 284 | 285 | /// @notice `msg.sender` approves `_spender` to send `_amount` tokens on 286 | /// its behalf, and then a function is triggered in the contract that is 287 | /// being approved, `_spender`. This allows users to use their tokens to 288 | /// interact with contracts in one function call instead of two 289 | /// @param _spender The address of the contract able to transfer the tokens 290 | /// @param _amount The amount of tokens to be approved for transfer 291 | /// @return True if the function call was successful 292 | function approveAndCall(address _spender, uint256 _amount, bytes _extraData 293 | ) returns (bool success) { 294 | if (!approve(_spender, _amount)) throw; 295 | 296 | ApproveAndCallFallBack(_spender).receiveApproval( 297 | msg.sender, 298 | _amount, 299 | this, 300 | _extraData 301 | ); 302 | 303 | return true; 304 | } 305 | 306 | /// @dev This function makes it easy to get the total number of tokens 307 | /// @return The total number of tokens 308 | function totalSupply() constant returns (uint) { 309 | return totalSupplyAt(block.number); 310 | } 311 | 312 | 313 | //////////////// 314 | // Query balance and totalSupply in History 315 | //////////////// 316 | 317 | /// @dev Queries the balance of `_owner` at a specific `_blockNumber` 318 | /// @param _owner The address from which the balance will be retrieved 319 | /// @param _blockNumber The block number when the balance is queried 320 | /// @return The balance at `_blockNumber` 321 | function balanceOfAt(address _owner, uint _blockNumber) constant 322 | returns (uint) { 323 | 324 | // These next few lines are used when the balance of the token is 325 | // requested before a check point was ever created for this token, it 326 | // requires that the `parentToken.balanceOfAt` be queried at the 327 | // genesis block for that token as this contains initial balance of 328 | // this token 329 | if ((balances[_owner].length == 0) 330 | || (balances[_owner][0].fromBlock > _blockNumber)) { 331 | if (address(parentToken) != 0) { 332 | return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock)); 333 | } else { 334 | // Has no parent 335 | return 0; 336 | } 337 | 338 | // This will return the expected balance during normal situations 339 | } else { 340 | return getValueAt(balances[_owner], _blockNumber); 341 | } 342 | } 343 | 344 | /// @notice Total amount of tokens at a specific `_blockNumber`. 345 | /// @param _blockNumber The block number when the totalSupply is queried 346 | /// @return The total amount of tokens at `_blockNumber` 347 | function totalSupplyAt(uint _blockNumber) constant returns(uint) { 348 | 349 | // These next few lines are used when the totalSupply of the token is 350 | // requested before a check point was ever created for this token, it 351 | // requires that the `parentToken.totalSupplyAt` be queried at the 352 | // genesis block for this token as that contains totalSupply of this 353 | // token at this block number. 354 | if ((totalSupplyHistory.length == 0) 355 | || (totalSupplyHistory[0].fromBlock > _blockNumber)) { 356 | if (address(parentToken) != 0) { 357 | return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock)); 358 | } else { 359 | return 0; 360 | } 361 | 362 | // This will return the expected totalSupply during normal situations 363 | } else { 364 | return getValueAt(totalSupplyHistory, _blockNumber); 365 | } 366 | } 367 | 368 | //////////////// 369 | // Clone Token Method 370 | //////////////// 371 | 372 | /// @notice Creates a new clone token with the initial distribution being 373 | /// this token at `_snapshotBlock` 374 | /// @param _cloneTokenName Name of the clone token 375 | /// @param _cloneDecimalUnits Number of decimals of the smallest unit 376 | /// @param _cloneTokenSymbol Symbol of the clone token 377 | /// @param _snapshotBlock Block when the distribution of the parent token is 378 | /// copied to set the initial distribution of the new clone token; 379 | /// if the block is higher than the actual block, the current block is used 380 | /// @param _transfersEnabled True if transfers are allowed in the clone 381 | /// @return The address of the new MiniMeToken Contract 382 | function createCloneToken( 383 | string _cloneTokenName, 384 | uint8 _cloneDecimalUnits, 385 | string _cloneTokenSymbol, 386 | uint _snapshotBlock, 387 | bool _transfersEnabled 388 | ) returns(address) { 389 | if (_snapshotBlock > block.number) _snapshotBlock = block.number; 390 | MiniMeToken cloneToken = tokenFactory.createCloneToken( 391 | this, 392 | _snapshotBlock, 393 | _cloneTokenName, 394 | _cloneDecimalUnits, 395 | _cloneTokenSymbol, 396 | _transfersEnabled 397 | ); 398 | 399 | cloneToken.changeController(msg.sender); 400 | 401 | // An event to make the token easy to find on the blockchain 402 | NewCloneToken(address(cloneToken), _snapshotBlock); 403 | return address(cloneToken); 404 | } 405 | 406 | //////////////// 407 | // Generate and destroy tokens 408 | //////////////// 409 | 410 | /// @notice Generates `_amount` tokens that are assigned to `_owner` 411 | /// @param _owner The address that will be assigned the new tokens 412 | /// @param _amount The quantity of tokens generated 413 | /// @return True if the tokens are generated correctly 414 | function generateTokens(address _owner, uint _amount 415 | ) onlyController returns (bool) { 416 | uint curTotalSupply = getValueAt(totalSupplyHistory, block.number); 417 | if (curTotalSupply + _amount < curTotalSupply) throw; // Check for overflow 418 | updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); 419 | var previousBalanceTo = balanceOf(_owner); 420 | if (previousBalanceTo + _amount < previousBalanceTo) throw; // Check for overflow 421 | updateValueAtNow(balances[_owner], previousBalanceTo + _amount); 422 | Transfer(0, _owner, _amount); 423 | return true; 424 | } 425 | 426 | 427 | /// @notice Burns `_amount` tokens from `_owner` 428 | /// @param _owner The address that will lose the tokens 429 | /// @param _amount The quantity of tokens to burn 430 | /// @return True if the tokens are burned correctly 431 | function destroyTokens(address _owner, uint _amount 432 | ) onlyController returns (bool) { 433 | uint curTotalSupply = getValueAt(totalSupplyHistory, block.number); 434 | if (curTotalSupply < _amount) throw; 435 | updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount); 436 | var previousBalanceFrom = balanceOf(_owner); 437 | if (previousBalanceFrom < _amount) throw; 438 | updateValueAtNow(balances[_owner], previousBalanceFrom - _amount); 439 | Transfer(_owner, 0, _amount); 440 | return true; 441 | } 442 | 443 | //////////////// 444 | // Enable tokens transfers 445 | //////////////// 446 | 447 | 448 | /// @notice Enables token holders to transfer their tokens freely if true 449 | /// @param _transfersEnabled True if transfers are allowed in the clone 450 | function enableTransfers(bool _transfersEnabled) onlyController { 451 | transfersEnabled = _transfersEnabled; 452 | } 453 | 454 | //////////////// 455 | // Internal helper functions to query and set a value in a snapshot array 456 | //////////////// 457 | 458 | /// @dev `getValueAt` retrieves the number of tokens at a given block number 459 | /// @param checkpoints The history of values being queried 460 | /// @param _block The block number to retrieve the value at 461 | /// @return The number of tokens being queried 462 | function getValueAt(Checkpoint[] storage checkpoints, uint _block 463 | ) constant internal returns (uint) { 464 | if (checkpoints.length == 0) return 0; 465 | 466 | // Shortcut for the actual value 467 | if (_block >= checkpoints[checkpoints.length-1].fromBlock) 468 | return checkpoints[checkpoints.length-1].value; 469 | if (_block < checkpoints[0].fromBlock) return 0; 470 | 471 | // Binary search of the value in the array 472 | uint min = 0; 473 | uint max = checkpoints.length-1; 474 | while (max > min) { 475 | uint mid = (max + min + 1)/ 2; 476 | if (checkpoints[mid].fromBlock<=_block) { 477 | min = mid; 478 | } else { 479 | max = mid-1; 480 | } 481 | } 482 | return checkpoints[min].value; 483 | } 484 | 485 | /// @dev `updateValueAtNow` used to update the `balances` map and the 486 | /// `totalSupplyHistory` 487 | /// @param checkpoints The history of data being updated 488 | /// @param _value The new number of tokens 489 | function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value 490 | ) internal { 491 | if ((checkpoints.length == 0) 492 | || (checkpoints[checkpoints.length -1].fromBlock < block.number)) { 493 | Checkpoint newCheckPoint = checkpoints[ checkpoints.length++ ]; 494 | newCheckPoint.fromBlock = uint128(block.number); 495 | newCheckPoint.value = uint128(_value); 496 | } else { 497 | Checkpoint oldCheckPoint = checkpoints[checkpoints.length-1]; 498 | oldCheckPoint.value = uint128(_value); 499 | } 500 | } 501 | 502 | /// @dev Internal function to determine if an address is a contract 503 | /// @param _addr The address being queried 504 | /// @return True if `_addr` is a contract 505 | function isContract(address _addr) constant internal returns(bool) { 506 | uint size; 507 | if (_addr == 0) return false; 508 | assembly { 509 | size := extcodesize(_addr) 510 | } 511 | return size>0; 512 | } 513 | 514 | /// @dev Helper function to return a min betwen the two uints 515 | function min(uint a, uint b) internal returns (uint) { 516 | return a < b ? a : b; 517 | } 518 | 519 | /// @notice The fallback function: If the contract's controller has not been 520 | /// set to 0, then the `proxyPayment` method is called which relays the 521 | /// ether and creates tokens as described in the token controller contract 522 | function () payable { 523 | if (isContract(controller)) { 524 | if (! TokenController(controller).proxyPayment.value(msg.value)(msg.sender)) 525 | throw; 526 | } else { 527 | throw; 528 | } 529 | } 530 | 531 | 532 | //////////////// 533 | // Events 534 | //////////////// 535 | event Transfer(address indexed _from, address indexed _to, uint256 _amount); 536 | event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock); 537 | event Approval( 538 | address indexed _owner, 539 | address indexed _spender, 540 | uint256 _amount 541 | ); 542 | 543 | } 544 | 545 | 546 | //////////////// 547 | // MiniMeTokenFactory 548 | //////////////// 549 | 550 | /// @dev This contract is used to generate clone contracts from a contract. 551 | /// In solidity this is the way to create a contract from a contract of the 552 | /// same class 553 | contract MiniMeTokenFactory { 554 | 555 | /// @notice Update the DApp by creating a new token with new functionalities 556 | /// the msg.sender becomes the controller of this clone token 557 | /// @param _parentToken Address of the token being cloned 558 | /// @param _snapshotBlock Block of the parent token that will 559 | /// determine the initial distribution of the clone token 560 | /// @param _tokenName Name of the new token 561 | /// @param _decimalUnits Number of decimals of the new token 562 | /// @param _tokenSymbol Token Symbol for the new token 563 | /// @param _transfersEnabled If true, tokens will be able to be transferred 564 | /// @return The address of the new token contract 565 | function createCloneToken( 566 | address _parentToken, 567 | uint _snapshotBlock, 568 | string _tokenName, 569 | uint8 _decimalUnits, 570 | string _tokenSymbol, 571 | bool _transfersEnabled 572 | ) returns (MiniMeToken) { 573 | MiniMeToken newToken = new MiniMeToken( 574 | this, 575 | _parentToken, 576 | _snapshotBlock, 577 | _tokenName, 578 | _decimalUnits, 579 | _tokenSymbol, 580 | _transfersEnabled 581 | ); 582 | 583 | newToken.changeController(msg.sender); 584 | return newToken; 585 | } 586 | } -------------------------------------------------------------------------------- /utility.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | /* eslint no-console: ["error", { allow: ["log"] }] */ 3 | 4 | // const fs = require('fs'); 5 | // const request = require('request'); 6 | // const async = typeof window === 'undefined' ? require('async') : require('async/dist/async.min.js'); 7 | // const Web3 = require('web3'); 8 | // const SolidityFunction = require('web3/lib/web3/function.js'); 9 | // const SolidityEvent = require('web3/lib/web3/event.js'); 10 | // const coder = require('web3/lib/solidity/coder.js'); 11 | // const utils = require('web3/lib/utils/utils.js'); 12 | const sha3 = require('web3/lib/utils/sha3.js'); 13 | const sha256 = require('js-sha256').sha256; 14 | const Tx = require('ethereumjs-tx'); 15 | // const keythereum = require('keythereum'); 16 | const ethUtil = require('ethereumjs-util'); 17 | const BigNumber = require('bignumber.js'); 18 | const IgasStation = require('./build/contracts/IgasStation.json'); 19 | const IMiniMeToken = require('./build/contracts/IMiniMeToken.json'); 20 | 21 | module.exports = (config) => { 22 | const utility = {}; 23 | 24 | 25 | 26 | // utility.weiToEth = function weiToEth(wei, divisorIn) { 27 | // const divisor = !divisorIn ? 1000000000000000000 : divisorIn; 28 | // return (wei / divisor).toFixed(3); 29 | // }; 30 | 31 | // utility.ethToWei = function ethToWei(eth, divisorIn) { 32 | // const divisor = !divisorIn ? 1000000000000000000 : divisorIn; 33 | // return parseFloat((eth * divisor).toPrecision(10)); 34 | // }; 35 | 36 | // utility.roundToNearest = function roundToNearest(numToRound, numToRoundToIn) { 37 | // const numToRoundTo = 1 / numToRoundToIn; 38 | // return Math.round(numToRound * numToRoundTo) / numToRoundTo; 39 | // }; 40 | 41 | // utility.getURL = function getURL(url, callback, options) { 42 | // request.get(url, options, (err, httpResponse, body) => { 43 | // if (err) { 44 | // callback(err, undefined); 45 | // } else { 46 | // callback(undefined, body); 47 | // } 48 | // }); 49 | // }; 50 | 51 | // utility.postURL = function postURL(url, formData, callback) { 52 | // request.post({ url, form: formData }, (err, httpResponse, body) => { 53 | // if (err) { 54 | // callback(err, undefined); 55 | // } else { 56 | // callback(undefined, body); 57 | // } 58 | // }); 59 | // }; 60 | 61 | // utility.readFile = function readFile( // eslint-disable-line consistent-return 62 | // filename, callback) { 63 | // if (callback) { 64 | // try { 65 | // if (typeof window === 'undefined') { 66 | // fs.readFile(filename, { encoding: 'utf8' }, (err, data) => { 67 | // if (err) { 68 | // callback(err, undefined); 69 | // } else { 70 | // callback(undefined, data); 71 | // } 72 | // }); 73 | // } else { 74 | // utility.getURL(`${config.homeURL}/${filename}`, (err, body) => { 75 | // if (err) { 76 | // callback(err, undefined); 77 | // } else { 78 | // callback(undefined, body); 79 | // } 80 | // }); 81 | // } 82 | // } catch (err) { 83 | // callback(err, undefined); 84 | // } 85 | // } else { 86 | // try { 87 | // return fs.readFileSync(filename, { encoding: 'utf8' }); 88 | // } catch (err) { 89 | // return undefined; 90 | // } 91 | // } 92 | // }; 93 | 94 | // utility.writeFile = function writeFile(filename, data, callback) { 95 | // fs.writeFile(filename, data, (err) => { 96 | // if (err) { 97 | // callback(err, false); 98 | // } else { 99 | // callback(undefined, true); 100 | // } 101 | // }); 102 | // }; 103 | 104 | // utility.createCookie = function createCookie(name, value, days) { 105 | // if (localStorage) { 106 | // localStorage.setItem(name, value); 107 | // } else { 108 | // let expires; 109 | // if (days) { 110 | // const date = new Date(); 111 | // date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 112 | // expires = `; expires=${date.toGMTString()}`; 113 | // } else { 114 | // expires = ''; 115 | // } 116 | // document.cookie = `${name}=${value}${expires}; path=/`; 117 | // } 118 | // }; 119 | 120 | // utility.readCookie = function readCookie(name) { 121 | // if (localStorage) { 122 | // return localStorage.getItem(name); 123 | // } 124 | // const nameEQ = `${name}=`; 125 | // const ca = document.cookie.split(';'); 126 | // for (let i = 0; i < ca.length; i += 1) { 127 | // let c = ca[i]; 128 | // while (c.charAt(0) === ' ') { 129 | // c = c.substring(1, c.length); 130 | // } 131 | // if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); 132 | // } 133 | // return null; 134 | // }; 135 | 136 | // utility.eraseCookie = function eraseCookie(name) { 137 | // if (localStorage) { 138 | // localStorage.removeItem(name); 139 | // } else { 140 | // utility.createCookie(name, '', -1); 141 | // } 142 | // }; 143 | 144 | // utility.getNextNonce = function getNextNonce(web3, address, callback) { 145 | // function proxy() { 146 | // let url = 147 | // `https://${ 148 | // config.ethTestnet ? config.ethTestnet : 'api' 149 | // }.etherscan.io/api?module=proxy&action=eth_GetTransactionCount&address=${ 150 | // address 151 | // }&tag=latest`; 152 | // if (config.etherscanAPIKey) url += `&apikey=${config.etherscanAPIKey}`; 153 | // utility.getURL(url, (err, body) => { 154 | // if (!err) { 155 | // const result = JSON.parse(body); 156 | // const nextNonce = Number(result.result); 157 | // callback(undefined, nextNonce); 158 | // } else { 159 | // callback(err, undefined); 160 | // } 161 | // }); 162 | // } 163 | // try { 164 | // if (web3.currentProvider) { 165 | // web3.eth.getTransactionCount(address, (err, result) => { 166 | // if (!err) { 167 | // const nextNonce = Number(result); 168 | // // Note. initial nonce is 2^20 on testnet, 169 | // // but getTransactionCount already starts at 2^20. 170 | // callback(undefined, nextNonce); 171 | // } else { 172 | // proxy(); 173 | // } 174 | // }); 175 | // } else { 176 | // proxy(); 177 | // } 178 | // } catch (err) { 179 | // proxy(); 180 | // } 181 | // }; 182 | 183 | // utility.testCall = function testCall(web3, contract, address, functionName, args, callback) { 184 | // const options = {}; 185 | // options.data = contract[functionName].getData.apply(null, args); 186 | // options.to = address; 187 | // web3.eth.call(options, (err, result) => { 188 | // if (!err) { 189 | // const functionAbi = contract.abi.find(element => 190 | // element.name === functionName); 191 | // const solidityFunction = new SolidityFunction(web3.Eth, functionAbi, address); 192 | // callback(err, solidityFunction.unpackOutput(result)); 193 | // } else { 194 | // callback(err, result); 195 | // } 196 | // }); 197 | // }; 198 | 199 | // utility.call = function call(web3In, contract, address, functionName, args, callback) { 200 | // function proxy(retries) { 201 | // const web3 = new Web3(); 202 | // const data = contract[functionName].getData.apply(null, args); 203 | // let url = 204 | // `https://${ 205 | // config.ethTestnet ? config.ethTestnet : 'api' 206 | // }.etherscan.io/api?module=proxy&action=eth_Call&to=${ 207 | // address 208 | // }&data=${ 209 | // data}`; 210 | // if (config.etherscanAPIKey) url += `&apikey=${config.etherscanAPIKey}`; 211 | // utility.getURL(url, (err, body) => { 212 | // if (!err) { 213 | // try { 214 | // const result = JSON.parse(body); 215 | // const functionAbi = contract.abi.find(element => element.name === functionName); 216 | // const solidityFunction = new SolidityFunction(web3.Eth, functionAbi, address); 217 | // const resultUnpacked = solidityFunction.unpackOutput(result.result); 218 | // callback(undefined, resultUnpacked); 219 | // } catch (errJson) { 220 | // if (retries > 0) { 221 | // setTimeout(() => { 222 | // proxy(retries - 1); 223 | // }, 1000); 224 | // } else { 225 | // callback(err, undefined); 226 | // } 227 | // } 228 | // } else { 229 | // callback(err, undefined); 230 | // } 231 | // }); 232 | // } 233 | // try { 234 | // if (web3In.currentProvider) { 235 | // const data = contract[functionName].getData.apply(null, args); 236 | // web3In.eth.call({ to: address, data }, (err, result) => { 237 | // if (!err) { 238 | // const functionAbi = contract.abi.find(element => element.name === functionName); 239 | // const solidityFunction = new SolidityFunction(web3In.Eth, functionAbi, address); 240 | // try { 241 | // const resultUnpacked = solidityFunction.unpackOutput(result); 242 | // callback(undefined, resultUnpacked); 243 | // } catch (errJson) { 244 | // proxy(1); 245 | // } 246 | // } else { 247 | // proxy(1); 248 | // } 249 | // }); 250 | // } else { 251 | // proxy(1); 252 | // } 253 | // } catch (err) { 254 | // proxy(1); 255 | // } 256 | // }; 257 | 258 | // utility.testSend = function testSend( 259 | // web3, contract, address, functionName, argsIn, fromAddress, privateKey, nonce, callback) { 260 | // function encodeConstructorParams(abi, params) { 261 | // return abi.filter(json => 262 | // json.type === 'constructor' && json.inputs.length === params.length) 263 | // .map(json => 264 | // json.inputs.map(input => input.type)) 265 | // .map(types => coder.encodeParams(types, params))[0] || ''; 266 | // } 267 | // const args = Array.prototype.slice.call(argsIn).filter(a => a !== undefined); 268 | // let options = {}; 269 | // if (typeof (args[args.length - 1]) === 'object' && args[args.length - 1].gas) { 270 | // args[args.length - 1].gasPrice = config.ethGasPrice; 271 | // args[args.length - 1].gasLimit = args[args.length - 1].gas; 272 | // delete args[args.length - 1].gas; 273 | // } 274 | // if (utils.isObject(args[args.length - 1])) { 275 | // options = args.pop(); 276 | // } 277 | // if (functionName === 'constructor') { 278 | // if (options.data.slice(0, 2) !== '0x') { 279 | // options.data = `0x${options.data}`; 280 | // } 281 | // options.data += encodeConstructorParams(contract.abi, args); 282 | // } else { 283 | // options.to = address; 284 | // const functionAbi = contract.abi.find(element => element.name === functionName); 285 | // const inputTypes = functionAbi.inputs.map(x => x.type); 286 | // const typeName = inputTypes.join(); 287 | // const data = sha3(`${functionName}(${typeName})`).slice(0, 8) + coder.encodeParams(inputTypes, args); 288 | // options.data = `0x${data}`; 289 | // } 290 | // if (!options.from) options.from = fromAddress; 291 | // web3.eth.sendTransaction(options, (err, result) => { 292 | // callback(err, result); 293 | // }); 294 | // }; 295 | 296 | // utility.send = function send( 297 | // web3, 298 | // contract, 299 | // address, 300 | // functionName, 301 | // argsIn, 302 | // fromAddress, 303 | // privateKeyIn, 304 | // nonceIn, 305 | // callback) { 306 | // let privateKey = privateKeyIn; 307 | // if (privateKeyIn && privateKeyIn.substring(0, 2) === '0x') { 308 | // privateKey = privateKeyIn.substring(2, privateKeyIn.length); 309 | // } 310 | // function encodeConstructorParams(abi, params) { 311 | // return ( 312 | // abi 313 | // .filter(json => json.type === 'constructor' && json.inputs.length === params.length) 314 | // .map(json => json.inputs.map(input => input.type)) 315 | // .map(types => coder.encodeParams(types, params))[0] || '' 316 | // ); 317 | // } 318 | // const args = Array.prototype.slice.call(argsIn).filter(a => a !== undefined); 319 | // let options = {}; 320 | // if (typeof args[args.length - 1] === 'object' && args[args.length - 1].gas) { 321 | // args[args.length - 1].gasPrice = config.ethGasPrice; 322 | // args[args.length - 1].gasLimit = args[args.length - 1].gas; 323 | // delete args[args.length - 1].gas; 324 | // } 325 | // if (utils.isObject(args[args.length - 1])) { 326 | // options = args.pop(); 327 | // } 328 | // utility.getNextNonce(web3, fromAddress, (err, nextNonce) => { 329 | // let nonce = nonceIn; 330 | // if (nonceIn === undefined || nonceIn < nextNonce) { 331 | // nonce = nextNonce; 332 | // } 333 | // // console.log("Nonce:", nonce); 334 | // options.nonce = nonce; 335 | // if (functionName === 'constructor') { 336 | // if (options.data.slice(0, 2) !== '0x') { 337 | // options.data = `0x${options.data}`; 338 | // } 339 | // const encodedParams = encodeConstructorParams(contract.abi, args); 340 | // console.log(encodedParams); 341 | // options.data += encodedParams; 342 | // } else if (!contract || !functionName) { 343 | // options.to = address; 344 | // } else { 345 | // options.to = address; 346 | // const functionAbi = contract.abi.find(element => element.name === functionName); 347 | // const inputTypes = functionAbi.inputs.map(x => x.type); 348 | // const typeName = inputTypes.join(); 349 | // options.data = 350 | // `0x${ 351 | // sha3(`${functionName}(${typeName})`).slice(0, 8) 352 | // }${coder.encodeParams(inputTypes, args)}`; 353 | // } 354 | // let tx; 355 | // try { 356 | // tx = new Tx(options); 357 | // function proxy() { // eslint-disable-line no-inner-declarations 358 | // if (privateKey) { 359 | // utility.signTx(web3, fromAddress, tx, privateKey, (errSignTx, txSigned) => { 360 | // if (!errSignTx) { 361 | // const serializedTx = txSigned.serialize().toString('hex'); 362 | // const url = `https://${config.ethTestnet ? config.ethTestnet : 'api'}.etherscan.io/api`; 363 | // const formData = { module: 'proxy', action: 'eth_sendRawTransaction', hex: serializedTx }; 364 | // if (config.etherscanAPIKey) formData.apikey = config.etherscanAPIKey; 365 | // utility.postURL(url, formData, (errPostURL, body) => { 366 | // if (!errPostURL) { 367 | // try { 368 | // const result = JSON.parse(body); 369 | // if (result.result) { 370 | // callback(undefined, { txHash: result.result, nonce: nonce + 1 }); 371 | // } else if (result.error) { 372 | // callback(result.error.message, { txHash: undefined, nonce }); 373 | // } 374 | // } catch (errTry) { 375 | // callback(errTry, { txHash: undefined, nonce }); 376 | // } 377 | // } else { 378 | // callback(err, { txHash: undefined, nonce }); 379 | // } 380 | // }); 381 | // } else { 382 | // console.log(err); 383 | // callback('Failed to sign transaction', { txHash: undefined, nonce }); 384 | // } 385 | // }); 386 | // } else { 387 | // callback('Failed to sign transaction', { txHash: undefined, nonce }); 388 | // } 389 | // } 390 | // try { 391 | // if (web3.currentProvider) { 392 | // options.from = fromAddress; 393 | // options.gas = options.gasLimit; 394 | // delete options.gasLimit; 395 | // web3.eth.sendTransaction(options, (errSend, hash) => { 396 | // if (!errSend) { 397 | // callback(undefined, { txHash: hash, nonce: nonce + 1 }); 398 | // } else { 399 | // console.log(err); 400 | // proxy(); 401 | // } 402 | // }); 403 | // } else { 404 | // proxy(); 405 | // } 406 | // } catch (errSend) { 407 | // proxy(); 408 | // } 409 | // } catch (errCatch) { 410 | // callback(errCatch, { txHash: undefined, nonce }); 411 | // } 412 | // }); 413 | // }; 414 | 415 | // utility.estimateGas = function estimateGas( 416 | // web3, 417 | // contract, 418 | // address, 419 | // functionName, 420 | // argsIn, 421 | // fromAddress, 422 | // privateKeyIn, 423 | // nonceIn, 424 | // callback) { 425 | // let privateKey = privateKeyIn; 426 | // if (privateKeyIn && privateKeyIn.substring(0, 2) === '0x') { 427 | // privateKey = privateKeyIn.substring(2, privateKeyIn.length); 428 | // } 429 | // const args = Array.prototype.slice.call(argsIn).filter(a => a !== undefined); 430 | // let options = {}; 431 | // const functionAbi = contract.abi.find(element => element.name === functionName); 432 | // const inputTypes = functionAbi.inputs.map(x => x.type); 433 | // if (typeof args[args.length - 1] === 'object' && args[args.length - 1].gas) { 434 | // args[args.length - 1].gasPrice = config.ethGasPrice; 435 | // args[args.length - 1].gasLimit = args[args.length - 1].gas; 436 | // delete args[args.length - 1].gas; 437 | // } 438 | // if (args.length > inputTypes.length && utils.isObject(args[args.length - 1])) { 439 | // options = args[args.length - 1]; 440 | // } 441 | // utility.getNextNonce(web3, fromAddress, (err, nextNonce) => { 442 | // let nonce = nonceIn; 443 | // if (nonceIn === undefined) { 444 | // nonce = nextNonce; 445 | // } 446 | // options.nonce = nonce; 447 | // options.to = address; 448 | // const typeName = inputTypes.join(); 449 | // options.data = 450 | // `0x${ 451 | // sha3(`${functionName}(${typeName})`).slice(0, 8) 452 | // }${coder.encodeParams(inputTypes, args)}`; 453 | // const tx = new Tx(options); 454 | // utility.signTx(web3, fromAddress, tx, privateKey, (errSignTx, txSigned) => { 455 | // if (!errSignTx && txSigned) { 456 | // if (web3.currentProvider) { 457 | // try { 458 | // web3.eth.estimateGas(options, (errEstimateGas, result) => { 459 | // if (errEstimateGas) { 460 | // callback(err, undefined); 461 | // } else { 462 | // callback(undefined, result); 463 | // } 464 | // }); 465 | // } catch (errTry) { 466 | // callback(errTry, undefined); 467 | // } 468 | // } else { 469 | // callback('No provider set for web3', undefined); 470 | // } 471 | // } else { 472 | // callback('Failed to sign transaction', undefined); 473 | // } 474 | // }); 475 | // }); 476 | // }; 477 | 478 | // utility.txReceipt = function txReceipt(web3, txHash, callback) { 479 | // function proxy() { 480 | // let url = 481 | // `https://${ 482 | // config.ethTestnet ? config.ethTestnet : 'api' 483 | // }.etherscan.io/api?module=proxy&action=eth_GetTransactionReceipt&txhash=${ 484 | // txHash}`; 485 | // if (config.etherscanAPIKey) url += `&apikey=${config.etherscanAPIKey}`; 486 | // utility.getURL(url, (err, body) => { 487 | // if (!err) { 488 | // const result = JSON.parse(body); 489 | // callback(undefined, result.result); 490 | // } else { 491 | // callback(err, undefined); 492 | // } 493 | // }); 494 | // } 495 | // try { 496 | // if (web3.currentProvider) { 497 | // try { 498 | // web3.eth.getTransactionReceipt(txHash, (err, result) => { 499 | // if (err) { 500 | // proxy(); 501 | // } else { 502 | // callback(undefined, result); 503 | // } 504 | // }); 505 | // } catch (err) { 506 | // proxy(); 507 | // } 508 | // } else { 509 | // proxy(); 510 | // } 511 | // } catch (err) { 512 | // proxy(); 513 | // } 514 | // }; 515 | 516 | // utility.logsOnce = function logsOnce(web3, contract, address, fromBlock, toBlock, callback) { 517 | // function decodeEvent(item) { 518 | // const eventAbis = contract.abi.filter(eventAbi => ( 519 | // eventAbi.type === 'event' && 520 | // item.topics[0] === 521 | // `0x${ 522 | // sha3( 523 | // `${eventAbi.name 524 | // }(${ 525 | // eventAbi.inputs 526 | // .map(x => x.type) 527 | // .join() 528 | // })`)}` 529 | // )); 530 | // if (eventAbis.length > 0) { 531 | // const eventAbi = eventAbis[0]; 532 | // const event = new SolidityEvent(web3, eventAbi, address); 533 | // const result = event.decode(item); 534 | // return result; 535 | // } 536 | // return undefined; 537 | // } 538 | // function proxy(retries) { 539 | // let url = 540 | // `https://${ 541 | // config.ethTestnet ? config.ethTestnet : 'api' 542 | // }.etherscan.io/api?module=logs&action=getLogs&address=${ 543 | // address 544 | // }&fromBlock=${ 545 | // fromBlock 546 | // }&toBlock=${ 547 | // toBlock}`; 548 | // if (config.etherscanAPIKey) url += `&apikey=${config.etherscanAPIKey}`; 549 | // utility.getURL( 550 | // url, 551 | // (err, body) => { 552 | // if (!err) { 553 | // try { 554 | // const result = JSON.parse(body); 555 | // const items = result.result; 556 | // async.map( 557 | // items, 558 | // (item, callbackMap) => { 559 | // Object.assign(item, { 560 | // blockNumber: utility.hexToDec(item.blockNumber), 561 | // logIndex: utility.hexToDec(item.logIndex), 562 | // transactionIndex: utility.hexToDec(item.transactionIndex), 563 | // }); 564 | // const event = decodeEvent(item); 565 | // callbackMap(null, event); 566 | // }, 567 | // (errMap, events) => { 568 | // callback(null, events); 569 | // }); 570 | // } catch (errTry) { 571 | // if (retries > 0) { 572 | // proxy(retries - 1); 573 | // } else { 574 | // callback(null, []); 575 | // } 576 | // } 577 | // } else { 578 | // callback(null, []); 579 | // } 580 | // // }, 581 | // // { timeout: 1500 }); 582 | // }); 583 | // } 584 | // proxy(1); 585 | // }; 586 | 587 | // utility.getBalance = function getBalance(web3, address, callback) { 588 | // function proxy() { 589 | // let url = 590 | // `https://${ 591 | // config.ethTestnet ? config.ethTestnet : 'api' 592 | // }.etherscan.io/api?module=account&action=balance&address=${ 593 | // address 594 | // }&tag=latest`; 595 | // if (config.etherscanAPIKey) url += `&apikey=${config.etherscanAPIKey}`; 596 | // utility.getURL(url, (err, body) => { 597 | // if (!err) { 598 | // const result = JSON.parse(body); 599 | // const balance = new BigNumber(result.result); 600 | // callback(undefined, balance); 601 | // } else { 602 | // callback(err, undefined); 603 | // } 604 | // }); 605 | // } 606 | // try { 607 | // if (web3.currentProvider) { 608 | // web3.eth.getBalance(address, (err, balance) => { 609 | // if (!err) { 610 | // callback(undefined, balance); 611 | // } else { 612 | // proxy(); 613 | // } 614 | // }); 615 | // } else { 616 | // proxy(); 617 | // } 618 | // } catch (err) { 619 | // proxy(); 620 | // } 621 | // }; 622 | 623 | // utility.getCode = function getCode(web3, address, callback) { 624 | // function proxy() { 625 | // let url = 626 | // `https://${ 627 | // config.ethTestnet ? config.ethTestnet : 'api' 628 | // }.etherscan.io/api?module=proxy&action=eth_getCode&address=${ 629 | // address 630 | // }&tag=latest`; 631 | // if (config.etherscanAPIKey) url += `&apikey=${config.etherscanAPIKey}`; 632 | // utility.getURL(url, (err, body) => { 633 | // if (!err) { 634 | // const result = JSON.parse(body); 635 | // callback(undefined, result.result); 636 | // } else { 637 | // callback(err, undefined); 638 | // } 639 | // }); 640 | // } 641 | // try { 642 | // if (web3.currentProvider) { 643 | // web3.eth.getCode(address, (err, code) => { 644 | // if (!err) { 645 | // callback(undefined, code); 646 | // } else { 647 | // proxy(); 648 | // } 649 | // }); 650 | // } else { 651 | // proxy(); 652 | // } 653 | // } catch (err) { 654 | // proxy(); 655 | // } 656 | // }; 657 | 658 | // utility.blockNumber = function blockNumber(web3, callback) { 659 | // function proxy() { 660 | // let url = 661 | // `https://${ 662 | // config.ethTestnet ? config.ethTestnet : 'api' 663 | // }.etherscan.io/api?module=proxy&action=eth_BlockNumber`; 664 | // if (config.etherscanAPIKey) url += `&apikey=${config.etherscanAPIKey}`; 665 | // utility.getURL(url, (err, body) => { 666 | // if (!err) { 667 | // const result = JSON.parse(body); 668 | // callback(undefined, Number(utility.hexToDec(result.result))); 669 | // } else { 670 | // callback(err, undefined); 671 | // } 672 | // }); 673 | // } 674 | // if (web3.currentProvider) { 675 | // web3.eth.getBlockNumber((err, result) => { 676 | // if (!err) { 677 | // callback(undefined, Number(result)); 678 | // } else { 679 | // proxy(); 680 | // } 681 | // }); 682 | // } else { 683 | // proxy(); 684 | // } 685 | // }; 686 | 687 | // utility.signTx = function signTx(web3, address, txIn, privateKey, callback) { 688 | // const tx = txIn; 689 | // if (privateKey) { 690 | // tx.sign(new Buffer(privateKey, 'hex')); 691 | // callback(undefined, tx); 692 | // } else { 693 | // const msgHash = `0x${tx.hash(false).toString('hex')}`; 694 | // web3.eth.sign(address, msgHash, (err, sigResult) => { 695 | // if (!err) { 696 | // try { 697 | // const r = sigResult.slice(0, 66); 698 | // const s = `0x${sigResult.slice(66, 130)}`; 699 | // let v = web3.toDecimal(`0x${sigResult.slice(130, 132)}`); 700 | // if (v !== 27 && v !== 28) v += 27; 701 | // tx.r = r; 702 | // tx.s = s; 703 | // tx.v = v; 704 | // callback(undefined, tx); 705 | // } catch (errTry) { 706 | // callback(errTry, undefined); 707 | // } 708 | // } else { 709 | // callback(err, undefined); 710 | // } 711 | // }); 712 | // } 713 | // }; 714 | 715 | utility.sign = function sign(address, msgToSignIn, privateKeyIn, callback) { 716 | let msgToSign = msgToSignIn; 717 | if (msgToSign.substring(0, 2) !== '0x') msgToSign = `0x${msgToSign}`; 718 | 719 | function prefixMessage(msgIn) { 720 | let msg = msgIn; 721 | msg = new Buffer(msg.slice(2), 'hex'); 722 | msg = Buffer.concat([ 723 | new Buffer(`\x19Ethereum Signed Message:\n${msg.length.toString()}`), 724 | msg 725 | ]); 726 | //console.log('MSG TO BE HASHED 1', msg.toString('hex')); 727 | 728 | msg = sha3(`0x${msg.toString('hex')}`, { 729 | encoding: 'hex' 730 | }); 731 | msg = new Buffer((msg.substring(0, 2) === '0x') ? msg.slice(2) : msg, 'hex'); 732 | return `0x${msg.toString('hex')}`; 733 | } 734 | 735 | function testSig(msg, sig) { 736 | const recoveredAddress = 737 | `0x${ethUtil.pubToAddress(ethUtil.ecrecover(msg, sig.v, sig.r, sig.s)).toString('hex')}`; 738 | return recoveredAddress === address; 739 | } 740 | //if (privateKeyIn) { 741 | let privateKey = privateKeyIn; 742 | if (privateKey.substring(0, 2) === '0x') privateKey = privateKey.substring(2, privateKey.length); 743 | msgToSign = prefixMessage(msgToSign); 744 | try { 745 | const sig = ethUtil.ecsign( 746 | new Buffer(msgToSign.slice(2), 'hex'), 747 | new Buffer(privateKey, 'hex')); 748 | const r = `0x${sig.r.toString('hex')}`; 749 | const s = `0x${sig.s.toString('hex')}`; 750 | const v = sig.v; 751 | const result = { 752 | r, 753 | s, 754 | v 755 | }; 756 | callback(undefined, result); 757 | } catch (err) { 758 | callback(err, undefined); 759 | } 760 | // } else { 761 | // web3.version.getNode((error, node) => { 762 | // // these nodes still use old-style eth_sign 763 | // if ( 764 | // node && 765 | // (node.match('TestRPC') || 766 | // node.match('MetaMask')) 767 | // ) { 768 | // msgToSign = prefixMessage(msgToSign); 769 | // } 770 | // web3.eth.sign(address, msgToSign, (err, sigResult) => { 771 | // if (err) { 772 | // callback('Failed to sign message 1' + err, undefined); 773 | // } else { 774 | // const sigHash = sigResult; 775 | // const sig = ethUtil.fromRpcSig(sigHash); 776 | // let msg; 777 | // if ( 778 | // node && 779 | // (node.match('TestRPC') || 780 | // node.match('MetaMask')) 781 | // ) { 782 | // msg = new Buffer(msgToSign.slice(2), 'hex'); 783 | // } else { 784 | // msg = new Buffer(prefixMessage(msgToSign).slice(2), 'hex'); 785 | // } 786 | // if (testSig(msg, sig, address)) { 787 | // const r = `0x${sig.r.toString('hex')}`; 788 | // const s = `0x${sig.s.toString('hex')}`; 789 | // const v = sig.v; 790 | // const result = { r, s, v }; 791 | // callback(undefined, result); 792 | // } else { 793 | // callback('Failed to sign message', undefined); 794 | // } 795 | // } 796 | // }); 797 | // }); 798 | // } 799 | }; 800 | 801 | utility.verify = function verify(addressIn, // eslint-disable-line consistent-return 802 | v, rIn, sIn, valueIn, callback) { 803 | const address = addressIn.toLowerCase(); 804 | let r = rIn; 805 | let s = sIn; 806 | let value = valueIn; 807 | if (r.substring(0, 2) === '0x') r = r.substring(2, r.length); 808 | if (s.substring(0, 2) === '0x') s = s.substring(2, s.length); 809 | if (value.substring(0, 2) === '0x') value = value.substring(2, value.length); 810 | const pubKey = ethUtil.ecrecover( 811 | new Buffer(value, 'hex'), 812 | Number(v), 813 | new Buffer(r, 'hex'), 814 | new Buffer(s, 'hex')); 815 | const result = address === `0x${ethUtil.pubToAddress(new Buffer(pubKey, 'hex')).toString('hex')}`; 816 | if (callback) { 817 | callback(undefined, result); 818 | } else { 819 | return result; 820 | } 821 | }; 822 | 823 | utility.signgastankparameters = function(tokenaddress, gastankaddress, gastankclient, take, give, valid_until, random, privatekey) { 824 | if (privatekey.substring(0, 2) === '0x') privatekey = privatekey.substring(2, privatekey.length); 825 | const condensed = utility.pack( 826 | [ 827 | tokenaddress, 828 | gastankaddress, 829 | gastankclient, 830 | take, 831 | give, 832 | valid_until, 833 | random, 834 | ], [160, 160, 160, 256, 256, 256, 256]); 835 | const hash = sha256(new Buffer(condensed, 'hex')); 836 | console.log('tokenaddress', tokenaddress); 837 | console.log('gastankaddress', gastankaddress); 838 | console.log('gastankclient', gastankclient); 839 | console.log('take', take); 840 | console.log('give', give); 841 | console.log('valid_until', valid_until); 842 | console.log('random', random); 843 | console.log('my hash=', hash); 844 | const sig = ethUtil.ecsign( 845 | new Buffer(hash, 'hex'), 846 | new Buffer(privatekey, 'hex')); 847 | const r = `0x${sig.r.toString('hex')}`; 848 | const s = `0x${sig.s.toString('hex')}`; 849 | const v = sig.v; 850 | const result = { 851 | r, 852 | s, 853 | v 854 | }; 855 | return result; 856 | }; 857 | 858 | 859 | utility.getapprovaltx = function(web3, from, from_pk, token_address, tokenamount, to, gasprice, cb) { 860 | 861 | var minime = web3.eth.contract(IMiniMeToken.abi); 862 | var minimeInstance = minime.at(token_address); 863 | 864 | // minimeInstance.balanceOf(this.address, function(err, res) { 865 | // console.log('SWT balance is', res.toFormat(2)); 866 | // self.tokenbalance = res; 867 | // }); 868 | 869 | console.log('sending approval from ', from, 'for ', tokenamount, 'to', to); 870 | 871 | var txData = minimeInstance.approve.getData(to, tokenamount); 872 | 873 | web3.eth.estimateGas({ 874 | to: token_address, 875 | data: txData, 876 | from: from 877 | }, function(err, res) { 878 | if (err) { 879 | return cb(err); 880 | } 881 | var gasRequired = res; 882 | 883 | // get nonce 884 | web3.eth.getTransactionCount(from, function(err, nonce) { 885 | 886 | if (!nonce) { 887 | nonce = 0; 888 | } 889 | 890 | var txParams = { 891 | nonce: nonce++, 892 | gasPrice: gasprice, 893 | gasLimit: gasRequired, 894 | to: token_address, 895 | from: from, 896 | data: txData, 897 | chainId: 1 898 | }; 899 | 900 | var tx = new Tx(txParams); 901 | tx.sign(new Buffer(from_pk.slice(2), 'hex')); 902 | 903 | //var serializedTx = tx.serialize(); 904 | 905 | return cb(null, { 906 | signedtx: `0x${tx.serialize().toString('hex')}`, 907 | cost: txParams.gasPrice * txParams.gasLimit 908 | }); 909 | 910 | 911 | }); 912 | 913 | }); 914 | 915 | // return txData; 916 | 917 | }; 918 | 919 | // utility.createAccount = function createAccount() { 920 | // const dk = keythereum.create(); 921 | // let privateKey = dk.privateKey; 922 | // let address = ethUtil.privateToAddress(privateKey); 923 | // address = ethUtil.toChecksumAddress(address.toString('hex')); 924 | // privateKey = privateKey.toString('hex'); 925 | // return { address, privateKey }; 926 | // }; 927 | 928 | // utility.verifyPrivateKey = function verifyPrivateKey(addr, privateKeyIn) { 929 | // let privateKey = privateKeyIn; 930 | // if (privateKey && privateKey.substring(0, 2) !== '0x') { 931 | // privateKey = `0x${privateKey}`; 932 | // } 933 | // return ( 934 | // addr === ethUtil.toChecksumAddress(`0x${ethUtil.privateToAddress(privateKey).toString('hex')}`) 935 | // ); 936 | // }; 937 | 938 | // utility.toChecksumAddress = function toChecksumAddress(addrIn) { 939 | // let addr = addrIn; 940 | // if (addr && addr.substring(0, 2) !== '0x') { 941 | // addr = `0x${addr}`; 942 | // } 943 | // return ethUtil.toChecksumAddress(addr); 944 | // }; 945 | 946 | // utility.loadContract = function loadContract(web3, sourceCode, address, callback) { 947 | // utility.readFile(`${sourceCode}.interface`, (errAbi, resultAbi) => { 948 | // const abi = JSON.parse(resultAbi); 949 | // let contract = web3.eth.contract(abi); 950 | // contract = contract.at(address); 951 | // callback(undefined, contract); 952 | // }); 953 | // }; 954 | 955 | // utility.deployContract = function deployContract(web3, sourceFile, 956 | // contractName, constructorParams, address, callback) { 957 | // utility.readFile(`${sourceFile}.bytecode`, (errBytecode, resultBytecode) => { 958 | // utility.readFile(`${sourceFile}.interface`, (errAbi, resultAbi) => { 959 | // if (resultAbi && resultBytecode) { 960 | // const abi = JSON.parse(resultAbi); 961 | // const bytecode = JSON.parse(resultBytecode); 962 | // const contract = web3.eth.contract(abi); 963 | // utility.send( 964 | // web3, 965 | // contract, 966 | // undefined, 967 | // 'constructor', 968 | // constructorParams.concat([ 969 | // { from: address, data: bytecode, gas: 4700000, gasPrice: config.ethGasPrice }, 970 | // ]), 971 | // address, 972 | // undefined, 973 | // 0, 974 | // (errSend, result) => { 975 | // const txHash = result.txHash; 976 | // let contractAddr; 977 | // async.whilst( 978 | // () => contractAddr === undefined, 979 | // (callbackWhilst) => { 980 | // setTimeout(() => { 981 | // utility.txReceipt(web3, txHash, (err, receipt) => { 982 | // if (receipt) { 983 | // contractAddr = receipt.contractAddress; 984 | // } 985 | // callbackWhilst(null); 986 | // }); 987 | // }, 1 * 1000); 988 | // }, 989 | // () => { 990 | // callback(undefined, address); 991 | // }); 992 | // }); 993 | // } else { 994 | // callback('Could not load bytecode and ABI', undefined); 995 | // } 996 | // }); 997 | // }); 998 | // }; 999 | 1000 | utility.zeroPad = function zeroPad(num, places) { 1001 | const zero = (places - num.toString().length) + 1; 1002 | return Array(+(zero > 0 && zero)).join('0') + num; 1003 | }; 1004 | 1005 | utility.decToHex = function decToHex(dec, lengthIn) { 1006 | let length = lengthIn; 1007 | if (!length) length = 32; 1008 | if (dec < 0) { 1009 | // return convertBase((Math.pow(2, length) + decStr).toString(), 10, 16); 1010 | return (new BigNumber(2)).pow(length).add(new BigNumber(dec)).toString(16); 1011 | } 1012 | let result = null; 1013 | try { 1014 | result = utility.convertBase(dec.toString(), 10, 16); 1015 | } catch (err) { 1016 | result = null; 1017 | } 1018 | if (result) { 1019 | return result; 1020 | } 1021 | return (new BigNumber(dec)).toString(16); 1022 | }; 1023 | 1024 | // utility.hexToDec = function hexToDec(hexStrIn, length) { 1025 | // // length implies this is a two's complement number 1026 | // let hexStr = hexStrIn; 1027 | // if (hexStr.substring(0, 2) === '0x') hexStr = hexStr.substring(2); 1028 | // hexStr = hexStr.toLowerCase(); 1029 | // if (!length) { 1030 | // return utility.convertBase(hexStr, 16, 10); 1031 | // } 1032 | // const max = Math.pow(2, length); // eslint-disable-line no-restricted-properties 1033 | // const answer = utility.convertBase(hexStr, 16, 10); 1034 | // return answer > max / 2 ? max : answer; 1035 | // }; 1036 | 1037 | utility.pack = function pack(dataIn, lengths) { 1038 | let packed = ''; 1039 | const data = dataIn.map(x => x); 1040 | for (let i = 0; i < lengths.length; i += 1) { 1041 | if (typeof(data[i]) === 'string' && data[i].substring(0, 2) === '0x') { 1042 | if (data[i].substring(0, 2) === '0x') data[i] = data[i].substring(2); 1043 | packed += utility.zeroPad(data[i], lengths[i] / 4); 1044 | } else if (typeof(data[i]) !== 'number' && /[a-f]/.test(data[i])) { 1045 | if (data[i].substring(0, 2) === '0x') data[i] = data[i].substring(2); 1046 | packed += utility.zeroPad(data[i], lengths[i] / 4); 1047 | } else { 1048 | // packed += zeroPad(new BigNumber(data[i]).toString(16), lengths[i]/4); 1049 | packed += utility.zeroPad(utility.decToHex(data[i], lengths[i]), lengths[i] / 4); 1050 | } 1051 | } 1052 | return packed; 1053 | }; 1054 | 1055 | // utility.unpack = function unpack(str, lengths) { 1056 | // const data = []; 1057 | // let length = 0; 1058 | // for (let i = 0; i < lengths.length; i += 1) { 1059 | // data[i] = parseInt(utility.hexToDec(str.substr(length, lengths[i] / 4), lengths[i]), 10); 1060 | // length += lengths[i] / 4; 1061 | // } 1062 | // return data; 1063 | // }; 1064 | 1065 | utility.convertBase = function convertBase(str, fromBase, toBase) { 1066 | const digits = utility.parseToDigitsArray(str, fromBase); 1067 | if (digits === null) return null; 1068 | let outArray = []; 1069 | let power = [1]; 1070 | for (let i = 0; i < digits.length; i += 1) { 1071 | if (digits[i]) { 1072 | outArray = utility.add(outArray, 1073 | utility.multiplyByNumber(digits[i], power, toBase), toBase); 1074 | } 1075 | power = utility.multiplyByNumber(fromBase, power, toBase); 1076 | } 1077 | let out = ''; 1078 | for (let i = outArray.length - 1; i >= 0; i -= 1) { 1079 | out += outArray[i].toString(toBase); 1080 | } 1081 | if (out === '') out = 0; 1082 | return out; 1083 | }; 1084 | 1085 | utility.parseToDigitsArray = function parseToDigitsArray(str, base) { 1086 | const digits = str.split(''); 1087 | const ary = []; 1088 | for (let i = digits.length - 1; i >= 0; i -= 1) { 1089 | const n = parseInt(digits[i], base); 1090 | if (isNaN(n)) return null; 1091 | ary.push(n); 1092 | } 1093 | return ary; 1094 | }; 1095 | 1096 | utility.add = function add(x, y, base) { 1097 | const z = []; 1098 | const n = Math.max(x.length, y.length); 1099 | let carry = 0; 1100 | let i = 0; 1101 | while (i < n || carry) { 1102 | const xi = i < x.length ? x[i] : 0; 1103 | const yi = i < y.length ? y[i] : 0; 1104 | const zi = carry + xi + yi; 1105 | z.push(zi % base); 1106 | carry = Math.floor(zi / base); 1107 | i += 1; 1108 | } 1109 | return z; 1110 | }; 1111 | 1112 | utility.multiplyByNumber = function multiplyByNumber(numIn, x, base) { 1113 | let num = numIn; 1114 | if (num < 0) return null; 1115 | if (num === 0) return []; 1116 | let result = []; 1117 | let power = x; 1118 | while (true) { // eslint-disable-line no-constant-condition 1119 | if (num & 1) { // eslint-disable-line no-bitwise 1120 | result = utility.add(result, power, base); 1121 | } 1122 | num = num >> 1; // eslint-disable-line operator-assignment, no-bitwise 1123 | if (num === 0) break; 1124 | power = utility.add(power, power, base); 1125 | } 1126 | return result; 1127 | }; 1128 | 1129 | // utility.getRandomInt = function getRandomInt(min, max) { 1130 | // return Math.floor(Math.random() * (max - min)) + min; 1131 | // }; 1132 | 1133 | // /* eslint-disable */ 1134 | // if (!Object.prototype.find) { 1135 | // Object.values = function (obj) { 1136 | // return Object.keys(obj).map(key => obj[key]); 1137 | // }; 1138 | // } 1139 | 1140 | // if (!Array.prototype.find) { 1141 | // Array.prototype.find = function (predicate) { 1142 | // if (this === null) { 1143 | // throw new TypeError('Array.prototype.find called on null or undefined'); 1144 | // } 1145 | // if (typeof predicate !== 'function') { 1146 | // throw new TypeError('predicate must be a function'); 1147 | // } 1148 | // const list = Object(this); 1149 | // const length = list.length >>> 0; 1150 | // const thisArg = arguments[1]; 1151 | // let value; 1152 | 1153 | // for (const i = 0; i < length; i++) { 1154 | // value = list[i]; 1155 | // if (predicate.call(thisArg, value, i, list)) { 1156 | // return value; 1157 | // } 1158 | // } 1159 | // return undefined; 1160 | // }; 1161 | // } 1162 | 1163 | // if (typeof Object.assign !== 'function') { 1164 | // (function () { 1165 | // Object.assign = function (target) { 1166 | // if (target === undefined || target === null) { 1167 | // throw new TypeError('Cannot convert undefined or null to object'); 1168 | // } 1169 | 1170 | // const output = Object(target); 1171 | // for (const index = 1; index < arguments.length; index++) { 1172 | // const source = arguments[index]; 1173 | // if (source !== undefined && source !== null) { 1174 | // for (const nextKey in source) { 1175 | // if (source.hasOwnProperty(nextKey)) { 1176 | // output[nextKey] = source[nextKey]; 1177 | // } 1178 | // } 1179 | // } 1180 | // } 1181 | // return output; 1182 | // }; 1183 | // }()); 1184 | // } 1185 | 1186 | // Array.prototype.getUnique = function () { 1187 | // const u = {}, 1188 | // a = []; 1189 | // for (const i = 0, l = this.length; i < l; ++i) { 1190 | // if (u.hasOwnProperty(this[i])) { 1191 | // continue; 1192 | // } 1193 | // a.push(this[i]); 1194 | // u[this[i]] = 1; 1195 | // } 1196 | // return a; 1197 | // }; 1198 | 1199 | // Array.prototype.max = function () { 1200 | // return Math.max.apply(null, this); 1201 | // }; 1202 | 1203 | // Array.prototype.min = function () { 1204 | // return Math.min.apply(null, this); 1205 | // }; 1206 | 1207 | // Array.prototype.equals = function (b) { 1208 | // if (this === b) return true; 1209 | // if (this == null || b == null) return false; 1210 | // if (this.length != b.length) return false; 1211 | 1212 | // // If you don't care about the order of the elements inside 1213 | // // the array, you should sort both arrays here. 1214 | 1215 | // for (const i = 0; i < this.length; ++i) { 1216 | // if (this[i] !== b[i]) return false; 1217 | // } 1218 | // return true; 1219 | // }; 1220 | 1221 | // Math.sign = 1222 | // Math.sign || 1223 | // function (x) { 1224 | // x = +x; // convert to a number 1225 | // if (x === 0 || isNaN(x)) { 1226 | // return x; 1227 | // } 1228 | // return x > 0 ? 1 : -1; 1229 | // }; 1230 | 1231 | /* eslint-enable */ 1232 | return utility; 1233 | }; --------------------------------------------------------------------------------