Initial new UI source code import (#2607)

* address -> name mappings

* expanding, loading all coin details

* send use only actual BasicCoin tokens registered (any reg)

* sending token & accounts

* form styling updates

* send form layout in place

* coin send working as expected

* api subscriptions on multiple addresses

* bring in events

* simplify

* basic events display in-place, functionally complete

* basic functionality in-place

* fix horrible event address issue

* rwork display of events slightly

* test TLA availability

* table for owner -> tokens

* fix signature lookup address

* fix signature lookup address

* basic overview styling

* txhash links

* page layout adjustments

* background import

* adjust colors

* no global registration, simplify color selection

* updated styling

* connection dialog for "busy connecting"

* initial token connection - WIP

* init token updates take place

* basic test for manual token

* rework connection display

* allow updates of the secure token

* first stab at making the build build

* update runner tags

* fix linting issues

* skip tests requiring network (should be e2e, TODO)

* re-enable javascript tag/runner

* release push does the trick

* push to any branch, CI name

* javscript-test runner as well

* swap dependencies build requires test

* revert stages swap

* retrieve images associated with tokens

* remove js build deps order

* null image when hash = 0x0

* 6x64 images (hashes for registries)

* don't pass tokens as prop to IdentityIcon

* check images against content hash pictures

* cleanup signer after connection changes

* fix naming typo

* display unknownImages for balances (not available as content hash)

* unknownImage for transfer dialog

* basic githubhint layout

* single input for commit/filename

* ethcore_hashContent call

* lookup hash

* registration in place

* fixes

* events is using a proper table

* pass value through as-is

* stop wrongly using main app IdentityIcon

* NEVER export class instance functions

* alignment back to normal

* typo  in definition

* set & get images working (mostly)

* show content retrieval info

* set exitcode via ||

* use javascript:latest images

* disable npm progress bar

* rename phase I

* rename phase II

* only send build output to GitHub on major branches

* also run the build step as part of the test (until comprehensive)

* ci-specific build (no webpack progress)

* allow for account creation via recovery phrase

* display account uuid (where available), closes #2546

* connection dialog now shows up in dapps as well, closes #2538

* token images show up as expected

* IdentityName component added and deployed

* fix padding tests

* adjust tests to map to stricter 0x-prefixed hex

* render names via common component for the address -> name

* split lint into seperate script (early exit)

* test phases changed to lint, test & pack

* pack part of test phase

* remove files marked for deletion (cleanup)

* Signer cleanups, start moving in the direction of the rest

* add personal signer methods

* basic signer request subscription

* don't poll blockNumber when not connected

* missing return, creating massive ws queue backlogs

* ΞTH -> ETH

* fix failing tests

* registry uses setAddress to actually set addresses now

* bytes mapping operates on lowerCase hex strings

* sha3 ids for each application

* add dappreg to list of contracts

* adjust alignment of queries

* show gas estimation log

* abi with payable for register function

* add key as required

* image retrieval from dappreg

* use proper Image urls

* embed and link apps from Parity, retrieved via /api/apps

* filter apps that has been replaced

* proxy entry for parity-utils

* add basiccoin abi

* add support for fallback abi type

* capture constructor paramaters

* merge master into js

* move images to assets/images/

* add font assets

* import fonts as part of build

* don't inline woff files

* Revert "merge master into js"

This reverts commit cfcfa81bd26f1b3cbc748d3afa1eb5c670b363fe.

* remove unused npm packages

* information on gas estimates (like almost everywhere else)

* don't pass gas & gasPrice to estimation

* display account passwordhint when available

* signer subscriptions based on polling & function trapping

* pending requests retrieved via jsapi

* update signer middleware

* remove all web3 instances

* remove web3 package

* last web3 dependencies removed

* no need to toChecksumAddress - api takes care of it

* expand description for personal_confirmRequest

* Signer conversion from web3 -> parity.js completed

* explicit in no return

* green circle background

* remove generated background

* convert /api/* paths to localhost:8080/api/* paths (hard-coded, temporary)

* change dapps to load from localhost:8080/ui/*

* remove dangling web3 files

* update manager test for signer

* /api/ping -> /

* additional token images

* additional token images

* add missing styles.css for 8180 error pages

* cater for txhash returning null/empty object

* adjust output directories

* Release merge with origin with ours strategy

* additional token images

* cater for development server

* s/localhost/127.0.0.1/ (cater for origin)

* Fix address selection for contract deployment

* Adjust z-index for error overlay

* better text on unique background pattern

* fix signer rejections

* Don't allow gavcoin transfer with no balance

* fix txhash rendering in signer

* remove unnecessary ParityBackground

* script to update js-precompiled

* Redirect from :8080 to :8180

* Remove extra return

* Dapp logo images
This commit is contained in:
Jaco Greeff
2016-10-18 11:52:56 +02:00
committed by Gav Wood
parent 6c7af57529
commit 1e6a2cb378
969 changed files with 57315 additions and 0 deletions

73
js/src/3rdparty/etherscan/account.js vendored Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const PAGE_SIZE = 25;
import util from '../../api/util';
import { call } from './call';
function _call (method, params, test) {
return call('account', method, params, test);
}
function balance (address, test = false) {
return _call('balance', {
address: address,
tag: 'latest'
}, test).then((balance) => {
// same format as balancemulti below
return {
account: address,
balance: balance
};
});
}
function balances (addresses, test = false) {
return _call('balancemulti', {
address: addresses.join(','),
tag: 'latest'
}, test);
}
function transactions (address, page, test = false) {
// page offset from 0
return _call('txlist', {
address: address,
page: (page || 0) + 1,
offset: PAGE_SIZE,
sort: 'desc'
}, test).then((transactions) => {
return transactions.map((tx) => {
return {
from: util.toChecksumAddress(tx.from),
to: util.toChecksumAddress(tx.to),
hash: tx.hash,
blockNumber: tx.blockNumber,
timeStamp: tx.timeStamp,
value: tx.value
};
});
});
}
const account = {
balance: balance,
balances: balances,
transactions: transactions
};
export { account };

View File

@@ -0,0 +1,69 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import etherscan from './';
const TESTADDR = '0xbf885e2b55c6bcc84556a3c5f07d3040833c8d00';
describe.skip('etherscan/account', () => {
const checkBalance = function (balance, addr) {
expect(balance).to.be.ok;
expect(balance.account).to.equal(addr);
expect(balance.balance).to.be.ok;
};
it('retrieves an account balance', () => {
return etherscan.account
.balance(TESTADDR)
.then((balance) => {
checkBalance(balance, TESTADDR);
});
});
it('retrieves multi account balances', () => {
const addresses = ['0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae', TESTADDR];
return etherscan.account
.balances(addresses)
.then((balances) => {
expect(balances).to.be.ok;
expect(balances.length).to.equal(2);
balances.forEach((balance, idx) => {
checkBalance(balance, addresses[idx]);
});
});
});
describe('transactions', () => {
it('retrieves a list of transactions (default)', () => {
return etherscan.account
.transactions(TESTADDR)
.then((transactions) => {
expect(transactions).to.be.ok;
expect(transactions.length).to.equal(25);
});
});
it('retrieves a list of transactions (page 1)', () => {
return etherscan.account
.transactions(TESTADDR, 1)
.then((transactions) => {
expect(transactions).to.be.ok;
expect(transactions.length).to.equal(25);
});
});
});
});

51
js/src/3rdparty/etherscan/call.js vendored Normal file
View File

@@ -0,0 +1,51 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const options = {
method: 'GET',
headers: {
'Accept': 'application/json'
}
};
export function call (module, action, _params, test) {
const host = test ? 'testnet.etherscan.io' : 'api.etherscan.io';
let params = '';
if (_params) {
Object.keys(_params).map((param) => {
const value = _params[param];
params = `${params}&${param}=${value}`;
});
}
return fetch(`http://${host}/api?module=${module}&action=${action}${params}`, options)
.then((response) => {
if (response.status !== 200) {
throw { code: response.status, message: response.statusText }; // eslint-disable-line
}
return response.json();
})
.then((result) => {
if (result.message === 'NOTOK') {
throw { code: -1, message: result.result }; // eslint-disable-line
}
return result.result;
});
}

25
js/src/3rdparty/etherscan/index.js vendored Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { account } from './account';
import { stats } from './stats';
const etherscan = {
account: account,
stats: stats
};
export default etherscan;

36
js/src/3rdparty/etherscan/stats.js vendored Normal file
View File

@@ -0,0 +1,36 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { call } from './call';
function _call (action, test) {
return call('stats', action, null, test);
}
function price (test = false) {
return _call('ethprice', test);
}
function supply (test = false) {
return _call('ethsupply', test);
}
const stats = {
price: price,
supply: supply
};
export { stats };

35
js/src/3rdparty/etherscan/stats.spec.js vendored Normal file
View File

@@ -0,0 +1,35 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import etherscan from './';
describe.skip('etherscan/stats', () => {
it('retrieves the latest price', () => {
return etherscan.stats
.price()
.then((price) => {
expect(price).to.be.ok;
});
});
it('retrieves the ether total', () => {
return etherscan.stats
.supply()
.then((supply) => {
expect(supply).to.be.ok;
});
});
});

View File

@@ -0,0 +1,71 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import chai from 'chai';
import nock from 'nock';
global.expect = chai.expect; // eslint-disable-line no-undef
import 'isomorphic-fetch';
import es6Promise from 'es6-promise';
es6Promise.polyfill();
import initShapeshift from './';
import initRpc from './rpc';
const APIKEY = '0x123454321';
const shapeshift = initShapeshift(APIKEY);
const rpc = initRpc(APIKEY);
function mockget (requests) {
let scope = nock(rpc.ENDPOINT);
requests.forEach((request) => {
scope = scope
.get(`/${request.path}`)
.reply(request.code || 200, () => {
return request.reply;
});
});
return scope;
}
function mockpost (requests) {
let scope = nock(rpc.ENDPOINT);
requests.forEach((request) => {
scope = scope
.post(`/${request.path}`)
.reply(request.code || 200, (uri, body) => {
scope.body = scope.body || {};
scope.body[request.path] = body;
return request.reply;
});
});
return scope;
}
export {
APIKEY,
mockget,
mockpost,
shapeshift,
rpc
};

22
js/src/3rdparty/shapeshift/index.js vendored Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import initRpc from './rpc';
import initShapeshift from './shapeshift';
export default function (apikey) {
return initShapeshift(initRpc(apikey));
}

67
js/src/3rdparty/shapeshift/rpc.js vendored Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const ENDPOINT = 'https://cors.shapeshift.io';
function call (method, options) {
return fetch(`${ENDPOINT}/${method}`, options)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then((result) => {
if (result.error) {
throw new Error(result.error);
}
return result;
});
}
export default function (apiKey) {
function get (method) {
return call(method, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
}
function post (method, data) {
const params = Object.assign({}, { apiKey }, data);
const body = JSON.stringify(params);
return call(method, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Content-Length': body.length
},
body
});
}
return {
ENDPOINT,
get,
post
};
}

77
js/src/3rdparty/shapeshift/rpc.spec.js vendored Normal file
View File

@@ -0,0 +1,77 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { APIKEY, mockget, mockpost, rpc } from './helpers.spec.js';
describe('shapeshift/rpc', () => {
describe('GET', () => {
const REPLY = { test: 'this is some result' };
let scope;
let result;
beforeEach(() => {
scope = mockget([{ path: 'test', reply: REPLY }]);
return rpc
.get('test')
.then((_result) => {
result = _result;
});
});
it('does GET', () => {
expect(scope.isDone()).to.be.true;
});
it('retrieves the info', () => {
expect(result).to.deep.equal(REPLY);
});
});
describe('POST', () => {
const REPLY = { test: 'this is some result' };
let scope;
let result;
beforeEach(() => {
scope = mockpost([{ path: 'test', reply: REPLY }]);
return rpc
.post('test', { input: 'stuff' })
.then((_result) => {
result = _result;
});
});
it('does POST', () => {
expect(scope.isDone()).to.be.true;
});
it('retrieves the info', () => {
expect(result).to.deep.equal(REPLY);
});
it('passes the input object', () => {
expect(scope.body.test.input).to.equal('stuff');
});
it('passes the apikey specified', () => {
expect(scope.body.test.apiKey).to.equal(APIKEY);
});
});
});

View File

@@ -0,0 +1,93 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default function (rpc) {
const subscriptions = [];
function getCoins () {
return rpc.get('getcoins');
}
function getMarketInfo (pair) {
return rpc.get(`marketinfo/${pair}`);
}
function getStatus (depositAddress) {
return rpc.get(`txStat/${depositAddress}`);
}
function shift (toAddress, returnAddress, pair) {
return rpc.post('shift', {
withdrawal: toAddress,
pair: pair,
returnAddress: returnAddress
});
}
function subscribe (depositAddress, callback) {
const idx = subscriptions.length;
subscriptions.push({
depositAddress,
callback,
idx
});
}
function _getSubscriptionStatus (subscription) {
if (!subscription) {
return;
}
getStatus(subscription.depositAddress)
.then((result) => {
switch (result.status) {
case 'no_deposits':
case 'received':
subscription.callback(null, result);
return;
case 'complete':
subscription.callback(null, result);
subscriptions[subscription.idx] = null;
return;
case 'failed':
subscription.callback({
message: status.error,
fatal: true
});
subscriptions[subscription.idx] = null;
return;
}
})
.catch(subscription.callback);
}
function _pollStatus () {
subscriptions.forEach(_getSubscriptionStatus);
}
setInterval(_pollStatus, 2000);
return {
getCoins,
getMarketInfo,
getStatus,
shift,
subscribe
};
}

View File

@@ -0,0 +1,124 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { mockget, mockpost, shapeshift } from './helpers.spec.js';
describe('shapeshift/calls', () => {
describe('getCoins', () => {
const REPLY = {
BTC: {
name: 'Bitcoin',
symbol: 'BTC',
image: 'https://shapeshift.io/images/coins/bitcoin.png',
status: 'available'
},
ETH: {
name: 'Ether',
symbol: 'ETH',
image: 'https://shapeshift.io/images/coins/ether.png',
status: 'available'
}
};
let scope;
before(() => {
scope = mockget([{ path: 'getcoins', reply: REPLY }]);
return shapeshift.getCoins();
});
it('makes the call', () => {
expect(scope.isDone()).to.be.ok;
});
});
describe('getMarketInfo', () => {
const REPLY = {
pair: 'btc_ltc',
rate: 128.17959917,
minerFee: 0.003,
limit: 0,
minimum: 0.00004632
};
let scope;
before(() => {
scope = mockget([{ path: 'marketinfo/btc_ltc', reply: REPLY }]);
return shapeshift.getMarketInfo('btc_ltc');
});
it('makes the call', () => {
expect(scope.isDone()).to.be.ok;
});
});
describe('getStatus', () => {
const REPLY = {
status: '0x123',
address: '0x123'
};
let scope;
before(() => {
scope = mockget([{ path: 'txStat/0x123', reply: REPLY }]);
return shapeshift.getStatus('0x123');
});
it('makes the call', () => {
expect(scope.isDone()).to.be.ok;
});
});
describe('shift', () => {
const REPLY = {
deposit: '1BTC',
depositType: 'btc',
withdrawal: '0x456',
withdrawalType: 'eth'
};
let scope;
before(() => {
scope = mockpost([{ path: 'shift', reply: REPLY }]);
return shapeshift.shift('0x456', '1BTC', 'btc_eth');
});
it('makes the call', () => {
expect(scope.isDone()).to.be.ok;
});
describe('body', () => {
it('has withdrawal set', () => {
expect(scope.body.shift.withdrawal).to.equal('0x456');
});
it('has returnAddress set', () => {
expect(scope.body.shift.returnAddress).to.equal('1BTC');
});
it('has pair set', () => {
expect(scope.body.shift.pair).to.equal('btc_eth');
});
});
});
});

32
js/src/abi/README.md Normal file
View File

@@ -0,0 +1,32 @@
# ethabi-js
A very early, very POC-type port of [https://github.com/ethcore/ethabi](https://github.com/ethcore/ethabi) to JavaScript
[![Build Status](https://travis-ci.org/jacogr/ethabi-js.svg?branch=master)](https://travis-ci.org/jacogr/ethabi-js)
[![Coverage Status](https://coveralls.io/repos/github/jacogr/ethabi-js/badge.svg?branch=master)](https://coveralls.io/github/jacogr/ethabi-js?branch=master)
[![Dependency Status](https://david-dm.org/jacogr/ethabi-js.svg)](https://david-dm.org/jacogr/ethabi-js)
[![devDependency Status](https://david-dm.org/jacogr/ethabi-js/dev-status.svg)](https://david-dm.org/jacogr/ethabi-js#info=devDependencies)
## contributing
Clone the repo and install dependencies via `npm install`. Tests can be executed via
- `npm run testOnce` (100% covered unit tests)
## installation
Install the package with `npm install --save ethabi-js` from the [npm registry ethabi-js](https://www.npmjs.com/package/ethabi-js)
## implementation
### approach
- this version tries to stay as close to the original Rust version in intent, function names & purpose
- it is a basic port of the Rust version, relying on effectively the same test-suite (expanded where deemed appropriate)
- it is meant as a library to be used in other projects, i.e. [ethapi-js](https://www.npmjs.com/package/ethapi-js)
### differences to original Rust version
- internally the library operates on string binary representations as opposed to Vector bytes, lengths are therefore 64 bytes as opposed to 32 bytes
- function names are adapted from the Rust standard snake_case to the JavaScript standard camelCase
- due to the initial library focus, the cli component (as implemented by the original) is not supported nor mplemented

20
js/src/abi/abi.js Normal file
View File

@@ -0,0 +1,20 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Interface from './spec/interface';
export default class Abi extends Interface {
}

View File

@@ -0,0 +1,30 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default class BytesTaken {
constructor (bytes, newOffset) {
this._bytes = bytes;
this._newOffset = newOffset;
}
get bytes () {
return this._bytes;
}
get newOffset () {
return this._newOffset;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BytesTaken from './bytesTaken';
describe('abi/decoder/BytesTaken', () => {
describe('constructor', () => {
it('sets the bytes of the object', () => {
expect((new BytesTaken(1, 2)).bytes).to.equal(1);
});
it('sets the newOffset of the object', () => {
expect((new BytesTaken(3, 4)).newOffset).to.equal(4);
});
});
});

View File

@@ -0,0 +1,30 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default class DecodeResult {
constructor (token, newOffset) {
this._token = token;
this._newOffset = newOffset;
}
get token () {
return this._token;
}
get newOffset () {
return this._newOffset;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import DecodeResult from './decodeResult';
describe('abi/decoder/DecodeResult', () => {
describe('constructor', () => {
it('sets the token of the object', () => {
expect((new DecodeResult('token', 2)).token).to.equal('token');
});
it('sets the newOffset of the object', () => {
expect((new DecodeResult('baz', 4)).newOffset).to.equal(4);
});
});
});

View File

@@ -0,0 +1,145 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import utf8 from 'utf8';
import Token from '../token/token';
import BytesTaken from './bytesTaken';
import DecodeResult from './decodeResult';
import ParamType from '../spec/paramType/paramType';
import { sliceData } from '../util/slice';
import { asAddress, asBool, asI32, asU32 } from '../util/sliceAs';
import { isArray, isInstanceOf } from '../util/types';
const NULL = '0000000000000000000000000000000000000000000000000000000000000000';
export default class Decoder {
static decode (params, data) {
if (!isArray(params)) {
throw new Error('Parameters should be array of ParamType');
}
const slices = sliceData(data);
let offset = 0;
return params.map((param) => {
const result = Decoder.decodeParam(param, slices, offset);
offset = result.newOffset;
return result.token;
});
}
static peek (slices, position) {
if (!slices || !slices[position]) {
return NULL;
}
return slices[position];
}
static takeBytes (slices, position, length) {
const slicesLength = Math.floor((length + 31) / 32);
let bytesStr = '';
for (let idx = 0; idx < slicesLength; idx++) {
bytesStr = `${bytesStr}${Decoder.peek(slices, position + idx)}`;
}
const bytes = (bytesStr.substr(0, length * 2).match(/.{1,2}/g) || []).map((code) => parseInt(code, 16));
return new BytesTaken(bytes, position + slicesLength);
}
static decodeParam (param, slices, offset) {
if (!isInstanceOf(param, ParamType)) {
throw new Error('param should be instanceof ParamType');
}
const tokens = [];
let taken;
let lengthOffset;
let length;
let newOffset;
switch (param.type) {
case 'address':
return new DecodeResult(new Token(param.type, asAddress(Decoder.peek(slices, offset))), offset + 1);
case 'bool':
return new DecodeResult(new Token(param.type, asBool(Decoder.peek(slices, offset))), offset + 1);
case 'int':
return new DecodeResult(new Token(param.type, asI32(Decoder.peek(slices, offset))), offset + 1);
case 'uint':
return new DecodeResult(new Token(param.type, asU32(Decoder.peek(slices, offset))), offset + 1);
case 'fixedBytes':
taken = Decoder.takeBytes(slices, offset, param.length);
return new DecodeResult(new Token(param.type, taken.bytes), taken.newOffset);
case 'bytes':
lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber();
length = asU32(Decoder.peek(slices, lengthOffset)).toNumber();
taken = Decoder.takeBytes(slices, lengthOffset + 1, length);
return new DecodeResult(new Token(param.type, taken.bytes), offset + 1);
case 'string':
if (param.indexed) {
taken = Decoder.takeBytes(slices, offset, 32);
return new DecodeResult(new Token('fixedBytes', taken.bytes), offset + 1);
}
lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber();
length = asU32(Decoder.peek(slices, lengthOffset)).toNumber();
taken = Decoder.takeBytes(slices, lengthOffset + 1, length);
const str = taken.bytes.map((code) => String.fromCharCode(code)).join('');
return new DecodeResult(new Token(param.type, utf8.decode(str)), offset + 1);
case 'array':
lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber();
length = asU32(Decoder.peek(slices, lengthOffset)).toNumber();
newOffset = lengthOffset + 1;
for (let idx = 0; idx < length; idx++) {
const result = Decoder.decodeParam(param.subtype, slices, newOffset);
newOffset = result.newOffset;
tokens.push(result.token);
}
return new DecodeResult(new Token(param.type, tokens), offset + 1);
case 'fixedArray':
newOffset = offset;
for (let idx = 0; idx < param.length; idx++) {
const result = Decoder.decodeParam(param.subtype, slices, newOffset);
newOffset = result.newOffset;
tokens.push(result.token);
}
return new DecodeResult(new Token(param.type, tokens), newOffset);
default:
throw new Error(`Invalid param type ${param.type} in decodeParam`);
}
}
}

View File

@@ -0,0 +1,310 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import Decoder from './decoder';
import ParamType from '../spec/paramType';
import Token from '../token';
import { padU32 } from '../util/pad';
describe('abi/decoder/Decoder', () => {
const stringToBytes = function (str) {
return str.match(/.{1,2}/g).map((code) => parseInt(code, 16));
};
const address1 = '0000000000000000000000001111111111111111111111111111111111111111';
const address2 = '0000000000000000000000002222222222222222222222222222222222222222';
const address3 = '0000000000000000000000003333333333333333333333333333333333333333';
const address4 = '0000000000000000000000004444444444444444444444444444444444444444';
const bool1 = '0000000000000000000000000000000000000000000000000000000000000001';
const bytes1 = '1234000000000000000000000000000000000000000000000000000000000000';
const bytes2 = '1000000000000000000000000000000000000000000000000000000000000000';
const bytes3 = '10000000000000000000000000000000000000000000000000000000000002';
const bytes4 = '0010000000000000000000000000000000000000000000000000000000000002';
const int1 = '0111111111111111111111111111111111111111111111111111111111111111';
const intn = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85';
const string1 = '6761766f66796f726b0000000000000000000000000000000000000000000000';
const tokenAddress1 = new Token('address', `0x${address1.slice(-40)}`);
const tokenAddress2 = new Token('address', `0x${address2.slice(-40)}`);
const tokenAddress3 = new Token('address', `0x${address3.slice(-40)}`);
const tokenAddress4 = new Token('address', `0x${address4.slice(-40)}`);
const tokenBool1 = new Token('bool', true);
const tokenFixedBytes1 = new Token('fixedBytes', [0x12, 0x34]);
const tokenBytes1 = new Token('bytes', [0x12, 0x34]);
const tokenBytes2 = new Token('bytes', stringToBytes(bytes2).concat(stringToBytes(bytes2)));
const tokenBytes3 = new Token('bytes', stringToBytes(bytes3));
const tokenBytes4 = new Token('bytes', stringToBytes(bytes4));
const tokenInt1 = new Token('int', new BigNumber(int1, 16));
const tokenIntn = new Token('int', new BigNumber(-123));
const tokenUint1 = new Token('uint', new BigNumber(int1, 16));
const tokenUintn = new Token('uint', new BigNumber(intn, 16));
const tokenString1 = new Token('string', 'gavofyork');
const slices = [ address1, address2, address3, address4 ];
describe('peek', () => {
it('returns the slice at the correct position', () => {
expect(Decoder.peek(slices, 1)).to.equal(slices[1]);
});
it('returns empty on invalid slices', () => {
expect(Decoder.peek(null, 4)).to.equal('0000000000000000000000000000000000000000000000000000000000000000');
});
});
describe('takeBytes', () => {
it('returns a single slice', () => {
expect(Decoder.takeBytes(slices, 0, 32).bytes).to.deep.equal(stringToBytes(slices[0]));
});
it('returns a single partial slice', () => {
expect(Decoder.takeBytes(slices, 0, 20).bytes).to.deep.equal(stringToBytes(slices[0].substr(0, 40)));
});
it('returns multiple slices', () => {
expect(Decoder.takeBytes(slices, 0, 64).bytes).to.deep.equal(stringToBytes(`${slices[0]}${slices[1]}`));
});
it('returns a single offset slice', () => {
expect(Decoder.takeBytes(slices, 1, 32).bytes).to.deep.equal(stringToBytes(slices[1]));
});
it('returns multiple offset slices', () => {
expect(Decoder.takeBytes(slices, 1, 64).bytes).to.deep.equal(stringToBytes(`${slices[1]}${slices[2]}`));
});
it('returns the requires length from slices', () => {
expect(
Decoder.takeBytes(slices, 1, 75).bytes
).to.deep.equal(stringToBytes(`${slices[1]}${slices[2]}${slices[3]}`.substr(0, 150)));
});
});
describe('decodeParam', () => {
it('throws an error on non ParamType param', () => {
expect(() => Decoder.decodeParam({})).to.throw(/ParamType/);
});
it('throws an error on invalid param type', () => {
const pt = new ParamType('address');
pt._type = 'noMatch';
expect(() => Decoder.decodeParam(pt)).to.throw(/noMatch/);
});
it('decodes an address', () => {
expect(
Decoder.decodeParam(new ParamType('address'), [address1], 0).token
).to.deep.equal(tokenAddress1);
});
it('decodes a bool', () => {
expect(
Decoder.decodeParam(new ParamType('bool'), [bool1], 0).token
).to.deep.equal(tokenBool1);
});
it('decodes an int', () => {
expect(
Decoder.decodeParam(new ParamType('int'), [int1], 0).token
).to.deep.equal(tokenInt1);
});
it('decodes a negative int', () => {
expect(
Decoder.decodeParam(new ParamType('int'), [intn], 0).token
).to.deep.equal(tokenIntn);
});
it('decodes an uint', () => {
expect(
Decoder.decodeParam(new ParamType('uint'), [int1], 0).token
).to.deep.equal(tokenUint1);
});
it('decodes an uint (negative as int)', () => {
expect(
Decoder.decodeParam(new ParamType('uint'), [intn], 0).token
).to.deep.equal(tokenUintn);
});
it('decodes fixedBytes', () => {
expect(
Decoder.decodeParam(new ParamType('fixedBytes', null, 2), [bytes1], 0).token
).to.deep.equal(tokenFixedBytes1);
});
it('decodes bytes', () => {
expect(
Decoder.decodeParam(new ParamType('bytes'), [padU32(0x20), padU32(2), bytes1], 0).token
).to.deep.equal(tokenBytes1);
});
it('decodes string', () => {
expect(
Decoder.decodeParam(new ParamType('string'), [padU32(0x20), padU32(9), string1], 0).token
).to.deep.equal(tokenString1);
});
it('decodes string (indexed)', () => {
expect(
Decoder.decodeParam(new ParamType('string', null, 0, true), [bytes1], 0)
).to.deep.equal(Decoder.decodeParam(new ParamType('fixedBytes', null, 32, true), [bytes1], 0));
});
});
describe('decode', () => {
it('throws an error on invalid params', () => {
expect(() => Decoder.decode(null, '123')).to.throw(/array/);
});
describe('address', () => {
it('decodes an address', () => {
expect(
Decoder.decode(
[new ParamType('address')],
`${address1}`
)
).to.deep.equal([tokenAddress1]);
});
it('decodes 2 addresses', () => {
expect(
Decoder.decode(
[new ParamType('address'), new ParamType('address')],
`${address1}${address2}`
)
).to.deep.equal([tokenAddress1, tokenAddress2]);
});
it('decodes a fixedArray of addresses', () => {
expect(
Decoder.decode(
[new ParamType('fixedArray', new ParamType('address'), 2)],
`${address1}${address2}`
)
).to.deep.equal([new Token('fixedArray', [tokenAddress1, tokenAddress2])]);
});
it('decodes a dynamic array of addresses', () => {
expect(
Decoder.decode(
[new ParamType('array', new ParamType('address'))],
`${padU32(0x20)}${padU32(2)}${address1}${address2}`
)
).to.deep.equal([new Token('array', [tokenAddress1, tokenAddress2])]);
});
it('decodes a dynamic array of fixed arrays', () => {
expect(
Decoder.decode(
[new ParamType('array', new ParamType('fixedArray', new ParamType('address'), 2))],
`${padU32(0x20)}${padU32(2)}${address1}${address2}${address3}${address4}`
)
).to.deep.equal([
new Token('array', [
new Token('fixedArray', [tokenAddress1, tokenAddress2]),
new Token('fixedArray', [tokenAddress3, tokenAddress4])
])
]);
});
});
describe('int', () => {
it('decodes an int', () => {
expect(
Decoder.decode(
[new ParamType('int')],
`${int1}`
)
).to.deep.equal([tokenInt1]);
});
});
describe('uint', () => {
it('decodes an uint', () => {
expect(
Decoder.decode(
[new ParamType('uint')],
`${int1}`
)
).to.deep.equal([tokenUint1]);
});
});
describe('fixedBytes', () => {
it('decodes fixedBytes', () => {
expect(
Decoder.decode(
[new ParamType('fixedBytes', null, 2)],
`${bytes1}`
)
).to.deep.equal([tokenFixedBytes1]);
});
});
describe('bytes', () => {
it('decodes bytes', () => {
expect(
Decoder.decode(
[new ParamType('bytes')],
`${padU32(0x20)}${padU32(2)}${bytes1}`
)
).to.deep.equal([tokenBytes1]);
});
it('decodes bytes sequence', () => {
expect(
Decoder.decode(
[new ParamType('bytes')],
`${padU32(0x20)}${padU32(0x40)}${bytes2}${bytes2}`
)
).to.deep.equal([tokenBytes2]);
});
it('decodes bytes seuence (2)', () => {
expect(
Decoder.decode(
[new ParamType('bytes'), new ParamType('bytes')],
`${padU32(0x40)}${padU32(0x80)}${padU32(0x1f)}${bytes3}00${padU32(0x20)}${bytes4}`
)
).to.deep.equal([tokenBytes3, tokenBytes4]);
});
});
describe('bool', () => {
it('decodes a single bool', () => {
expect(
Decoder.decode(
[new ParamType('bool')],
bool1
)
).to.deep.equal([tokenBool1]);
});
});
describe('string', () => {
it('decodes a string', () => {
expect(
Decoder.decode(
[new ParamType('string')],
`${padU32(0x20)}${padU32(9)}${string1}`
)
).to.deep.equal([tokenString1]);
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './decoder';

View File

@@ -0,0 +1,72 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { padAddress, padBool, padBytes, padFixedBytes, padU32, padString } from '../util/pad';
import Mediate from './mediate';
import Token from '../token/token';
import { isArray, isInstanceOf } from '../util/types';
export default class Encoder {
static encode (tokens) {
if (!isArray(tokens)) {
throw new Error('tokens should be array of Token');
}
const mediates = tokens.map((token) => Encoder.encodeToken(token));
const inits = mediates
.map((mediate, idx) => mediate.init(Mediate.offsetFor(mediates, idx)))
.join('');
const closings = mediates
.map((mediate, idx) => mediate.closing(Mediate.offsetFor(mediates, idx)))
.join('');
return `${inits}${closings}`;
}
static encodeToken (token) {
if (!isInstanceOf(token, Token)) {
throw new Error('token should be instanceof Token');
}
switch (token.type) {
case 'address':
return new Mediate('raw', padAddress(token.value));
case 'int':
case 'uint':
return new Mediate('raw', padU32(token.value));
case 'bool':
return new Mediate('raw', padBool(token.value));
case 'fixedBytes':
return new Mediate('raw', padFixedBytes(token.value));
case 'bytes':
return new Mediate('prefixed', padBytes(token.value));
case 'string':
return new Mediate('prefixed', padString(token.value));
case 'fixedArray':
case 'array':
return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token)));
default:
throw new Error(`Invalid token type ${token.type} in encodeToken`);
}
}
}

View File

@@ -0,0 +1,290 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Encoder from './encoder';
import Token from '../token';
import { padAddress, padFixedBytes, padU32 } from '../util/pad';
describe('abi/encoder/Encoder', () => {
describe('encodeToken', () => {
it('requires token as Token', () => {
expect(() => Encoder.encodeToken()).to.throw(/Token/);
});
it('encodes address tokens in Mediate(raw)', () => {
const mediate = Encoder.encodeToken(new Token('address', '123'));
expect(mediate.type).to.equal('raw');
expect(mediate.value).to.be.ok;
});
it('encodes bool tokens in Mediate(raw)', () => {
const mediatet = Encoder.encodeToken(new Token('bool', true));
const mediatef = Encoder.encodeToken(new Token('bool', false));
expect(mediatet.type).to.equal('raw');
expect(mediatet.value).to.be.ok;
expect(mediatef.type).to.equal('raw');
expect(mediatef.value).to.be.ok;
});
it('encodes int tokens in Mediate(raw)', () => {
const mediate = Encoder.encodeToken(new Token('int', '123'));
expect(mediate.type).to.equal('raw');
expect(mediate.value).to.be.ok;
});
it('encodes uint tokens in Mediate(raw)', () => {
const mediate = Encoder.encodeToken(new Token('uint', '123'));
expect(mediate.type).to.equal('raw');
expect(mediate.value).to.be.ok;
});
it('encodes fixedBytes tokens in Mediate(raw)', () => {
const mediate = Encoder.encodeToken(new Token('fixedBytes', '123'));
expect(mediate.type).to.equal('raw');
expect(mediate.value).to.be.ok;
});
it('encodes bytes tokens in Mediate(prefixed)', () => {
const mediate = Encoder.encodeToken(new Token('bytes', '123'));
expect(mediate.type).to.equal('prefixed');
expect(mediate.value).to.be.ok;
});
it('encodes string tokens in Mediate(prefixed)', () => {
const mediate = Encoder.encodeToken(new Token('string', '123'));
expect(mediate.type).to.equal('prefixed');
expect(mediate.value).to.be.ok;
});
it('encodes fixedArray tokens in Mediate(fixedArray)', () => {
const mediate = Encoder.encodeToken(new Token('fixedArray', [new Token('uint', '123')]));
expect(mediate.type).to.equal('fixedArray');
expect(mediate.value).to.be.ok;
});
it('encodes array tokens in Mediate(array)', () => {
const mediate = Encoder.encodeToken(new Token('array', [new Token('uint', '123')]));
expect(mediate.type).to.equal('array');
expect(mediate.value).to.be.ok;
});
it('throws an Error on invalid tokens', () => {
const token = new Token('address');
token._type = 'noMatch';
expect(() => Encoder.encodeToken(token)).to.throw(/noMatch/);
});
});
describe('encode', () => {
it('requires tokens array', () => {
expect(() => Encoder.encode()).to.throw(/array/);
});
describe('addresses', () => {
const address1 = '1111111111111111111111111111111111111111';
const address2 = '2222222222222222222222222222222222222222';
const address3 = '3333333333333333333333333333333333333333';
const address4 = '4444444444444444444444444444444444444444';
const encAddress1 = padAddress(address1);
const encAddress2 = padAddress(address2);
const encAddress3 = padAddress(address3);
const encAddress4 = padAddress(address4);
const tokenAddress1 = new Token('address', address1);
const tokenAddress2 = new Token('address', address2);
const tokenAddress3 = new Token('address', address3);
const tokenAddress4 = new Token('address', address4);
it('encodes an address', () => {
const token = tokenAddress1;
expect(Encoder.encode([token])).to.equal(encAddress1);
});
it('encodes an array of addresses', () => {
const expected = `${padU32(0x20)}${padU32(2)}${encAddress1}${encAddress2}`;
const token = new Token('array', [tokenAddress1, tokenAddress2]);
expect(Encoder.encode([token])).to.equal(expected);
});
it('encodes an fixedArray of addresses', () => {
const expected = `${encAddress1}${encAddress2}`;
const token = new Token('fixedArray', [tokenAddress1, tokenAddress2]);
expect(Encoder.encode([token])).to.equal(expected);
});
it('encodes two addresses', () => {
const expected = `${encAddress1}${encAddress2}`;
const tokens = [tokenAddress1, tokenAddress2];
expect(Encoder.encode(tokens)).to.equal(expected);
});
it('encodes fixed array of dynamic array addresses', () => {
const tokens1 = new Token('array', [tokenAddress1, tokenAddress2]);
const tokens2 = new Token('array', [tokenAddress3, tokenAddress4]);
const fixed = new Token('fixedArray', [tokens1, tokens2]);
const expected = `${padU32(0x40)}${padU32(0xa0)}${padU32(2)}${encAddress1}${encAddress2}${padU32(2)}${encAddress3}${encAddress4}`;
expect(Encoder.encode([fixed])).to.equal(expected);
});
it('encodes dynamic array of fixed array addresses', () => {
const tokens1 = new Token('fixedArray', [tokenAddress1, tokenAddress2]);
const tokens2 = new Token('fixedArray', [tokenAddress3, tokenAddress4]);
const dynamic = new Token('array', [tokens1, tokens2]);
const expected = `${padU32(0x20)}${padU32(2)}${encAddress1}${encAddress2}${encAddress3}${encAddress4}`;
expect(Encoder.encode([dynamic])).to.equal(expected);
});
it('encodes dynamic array of dynamic array addresses', () => {
const tokens1 = new Token('array', [tokenAddress1]);
const tokens2 = new Token('array', [tokenAddress2]);
const dynamic = new Token('array', [tokens1, tokens2]);
const expected = `${padU32(0x20)}${padU32(2)}${padU32(0x80)}${padU32(0xc0)}${padU32(1)}${encAddress1}${padU32(1)}${encAddress2}`;
expect(Encoder.encode([dynamic])).to.equal(expected);
});
it('encodes dynamic array of dynamic array addresses (2)', () => {
const tokens1 = new Token('array', [tokenAddress1, tokenAddress2]);
const tokens2 = new Token('array', [tokenAddress3, tokenAddress4]);
const dynamic = new Token('array', [tokens1, tokens2]);
const expected = `${padU32(0x20)}${padU32(2)}${padU32(0x80)}${padU32(0xe0)}${padU32(2)}${encAddress1}${encAddress2}${padU32(2)}${encAddress3}${encAddress4}`;
expect(Encoder.encode([dynamic])).to.equal(expected);
});
it('encodes fixed array of fixed array addresses', () => {
const tokens1 = new Token('fixedArray', [tokenAddress1, tokenAddress2]);
const tokens2 = new Token('fixedArray', [tokenAddress3, tokenAddress4]);
const dynamic = new Token('fixedArray', [tokens1, tokens2]);
const expected = `${encAddress1}${encAddress2}${encAddress3}${encAddress4}`;
expect(Encoder.encode([dynamic])).to.equal(expected);
});
});
describe('bytes', () => {
const bytes1 = '0x1234';
const bytes2 = '0x10000000000000000000000000000000000000000000000000000000000002';
const bytes3 = '0x1000000000000000000000000000000000000000000000000000000000000000';
it('encodes fixed bytes', () => {
const token = new Token('fixedBytes', bytes1);
expect(Encoder.encode([token])).to.equal(padFixedBytes(bytes1));
});
it('encodes bytes', () => {
const token = new Token('bytes', bytes1);
expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(2)}${padFixedBytes(bytes1)}`);
});
it('encodes bytes (short of boundary)', () => {
const token = new Token('bytes', bytes2);
expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(0x1f)}${padFixedBytes(bytes2)}`);
});
it('encodes bytes (two blocks)', () => {
const input = `${bytes3}${bytes3.slice(-64)}`;
const token = new Token('bytes', input);
expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(0x40)}${padFixedBytes(input)}`);
});
it('encodes two consecutive bytes', () => {
const in1 = '0x10000000000000000000000000000000000000000000000000000000000002';
const in2 = '0x0010000000000000000000000000000000000000000000000000000000000002';
const tokens = [new Token('bytes', in1), new Token('bytes', in2)];
expect(Encoder.encode(tokens)).to.equal(`${padU32(0x40)}${padU32(0x80)}${padU32(0x1f)}${padFixedBytes(in1)}${padU32(0x20)}${padFixedBytes(in2)}`);
});
});
describe('string', () => {
it('encodes a string', () => {
const string = 'gavofyork';
const stringEnc = padFixedBytes('0x6761766f66796f726b');
const token = new Token('string', string);
expect(Encoder.encode([token])).to.equal(`${padU32(0x20)}${padU32(string.length.toString(16))}${stringEnc}`);
});
});
describe('uint', () => {
it('encodes a uint', () => {
const token = new Token('uint', 4);
expect(Encoder.encode([token])).to.equal(padU32(4));
});
});
describe('int', () => {
it('encodes a int', () => {
const token = new Token('int', 4);
expect(Encoder.encode([token])).to.equal(padU32(4));
});
});
describe('bool', () => {
it('encodes a bool (true)', () => {
const token = new Token('bool', true);
expect(Encoder.encode([token])).to.equal(padU32(1));
});
it('encodes a bool (false)', () => {
const token = new Token('bool', false);
expect(Encoder.encode([token])).to.equal(padU32(0));
});
});
describe('comprehensive test', () => {
it('encodes a complex sequence', () => {
const bytes = '0x131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b';
const tokens = [new Token('int', 5), new Token('bytes', bytes), new Token('int', 3), new Token('bytes', bytes)];
expect(Encoder.encode(tokens)).to.equal(`${padU32(5)}${padU32(0x80)}${padU32(3)}${padU32(0xe0)}${padU32(0x40)}${bytes.substr(2)}${padU32(0x40)}${bytes.substr(2)}`);
});
it('encodes a complex sequence (nested)', () => {
const array = [new Token('int', 5), new Token('int', 6), new Token('int', 7)];
const tokens = [new Token('int', 1), new Token('string', 'gavofyork'), new Token('int', 2), new Token('int', 3), new Token('int', 4), new Token('array', array)];
const stringEnc = padFixedBytes('0x6761766f66796f726b');
expect(Encoder.encode(tokens)).to.equal(`${padU32(1)}${padU32(0xc0)}${padU32(2)}${padU32(3)}${padU32(4)}${padU32(0x100)}${padU32(9)}${stringEnc}${padU32(3)}${padU32(5)}${padU32(6)}${padU32(7)}`);
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './encoder';

View File

@@ -0,0 +1,142 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const TYPES = ['raw', 'prefixed', 'fixedArray', 'array'];
import { padU32 } from '../util/pad';
export default class Mediate {
constructor (type, value) {
Mediate.validateType(type);
this._type = type;
this._value = value;
}
initLength () {
switch (this._type) {
case 'raw':
return this._value.length / 2;
case 'array':
case 'prefixed':
return 32;
case 'fixedArray':
return this._value
.reduce((total, mediate) => {
return total + mediate.initLength();
}, 0);
}
}
closingLength () {
switch (this._type) {
case 'raw':
return 0;
case 'prefixed':
return this._value.length / 2;
case 'array':
return this._value
.reduce((total, mediate) => {
return total + mediate.initLength();
}, 32);
case 'fixedArray':
return this._value
.reduce((total, mediate) => {
return total + mediate.initLength() + mediate.closingLength();
}, 0);
}
}
init (suffixOffset) {
switch (this._type) {
case 'raw':
return this._value;
case 'fixedArray':
return this._value
.map((mediate, idx) => mediate.init(Mediate.offsetFor(this._value, idx)).toString(16))
.join('');
case 'prefixed':
case 'array':
return padU32(suffixOffset);
}
}
closing (offset) {
switch (this._type) {
case 'raw':
return '';
case 'prefixed':
return this._value;
case 'fixedArray':
return this._value
.map((mediate, idx) => mediate.closing(Mediate.offsetFor(this._value, idx)).toString(16))
.join('');
case 'array':
const prefix = padU32(this._value.length);
const inits = this._value
.map((mediate, idx) => mediate.init(offset + Mediate.offsetFor(this._value, idx) + 32).toString(16))
.join('');
const closings = this._value
.map((mediate, idx) => mediate.closing(offset + Mediate.offsetFor(this._value, idx)).toString(16))
.join('');
return `${prefix}${inits}${closings}`;
}
}
get type () {
return this._type;
}
get value () {
return this._value;
}
static offsetFor (mediates, position) {
if (position < 0 || position >= mediates.length) {
throw new Error(`Invalid position ${position} specified for Mediate.offsetFor`);
}
const initLength = mediates
.reduce((total, mediate) => {
return total + mediate.initLength();
}, 0);
return mediates
.slice(0, position)
.reduce((total, mediate) => {
return total + mediate.closingLength();
}, initLength);
}
static validateType (type) {
if (TYPES.filter((_type) => type === _type).length) {
return true;
}
throw new Error(`Invalid type ${type} received for Mediate.validateType`);
}
}

View File

@@ -0,0 +1,105 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Mediate from './mediate';
describe('abi/encoder/Mediate', () => {
const LONG15 = '1234567890abcdef000000000000000000000000000000000000000000000000';
const DOUBLE15 = `${LONG15}${LONG15}`;
const ARRAY = [new Mediate('raw', DOUBLE15), new Mediate('raw', LONG15)];
describe('validateType', () => {
it('validates raw', () => {
expect(Mediate.validateType('raw')).to.be.true;
});
it('validates prefixed', () => {
expect(Mediate.validateType('prefixed')).to.be.true;
});
it('validates fixedArray', () => {
expect(Mediate.validateType('fixedArray')).to.be.true;
});
it('validates array', () => {
expect(Mediate.validateType('array')).to.be.true;
});
it('throws an error on invalid types', () => {
expect(() => Mediate.validateType('noMatch')).to.throw(/noMatch/);
});
});
describe('offsetFor', () => {
it('thows an error when offset < 0', () => {
expect(() => Mediate.offsetFor([1], -1)).to.throw(/Invalid position/);
});
it('throws an error when offset >= length', () => {
expect(() => Mediate.offsetFor([1], 1)).to.throw(/Invalid position/);
});
});
describe('constructor', () => {
it('throws an error on invalid types', () => {
expect(() => new Mediate('noMatch', '1')).to.throw(/noMatch/);
});
it('sets the type of the object', () => {
expect((new Mediate('raw', '1')).type).to.equal('raw');
});
it('sets the value of the object', () => {
expect((new Mediate('raw', '1')).value).to.equal('1');
});
});
describe('initLength', () => {
it('returns correct variable byte length for raw', () => {
expect(new Mediate('raw', DOUBLE15).initLength()).to.equal(64);
});
it('returns correct fixed byte length for array', () => {
expect(new Mediate('array', [1, 2, 3, 4]).initLength()).to.equal(32);
});
it('returns correct fixed byte length for prefixed', () => {
expect(new Mediate('prefixed', 0).initLength()).to.equal(32);
});
it('returns correct variable byte length for fixedArray', () => {
expect(new Mediate('fixedArray', ARRAY).initLength()).to.equal(96);
});
});
describe('closingLength', () => {
it('returns 0 byte length for raw', () => {
expect(new Mediate('raw', DOUBLE15).closingLength()).to.equal(0);
});
it('returns prefix + size for prefixed', () => {
expect(new Mediate('prefixed', DOUBLE15).closingLength()).to.equal(64);
});
it('returns prefix + size for array', () => {
expect(new Mediate('array', ARRAY).closingLength()).to.equal(128);
});
it('returns total length for fixedArray', () => {
expect(new Mediate('fixedArray', ARRAY).closingLength()).to.equal(96);
});
});
});

17
js/src/abi/index.js Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './abi';

View File

@@ -0,0 +1,36 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Encoder from '../encoder/encoder';
import Param from './param';
export default class Constructor {
constructor (abi) {
this._inputs = Param.toParams(abi.inputs || []);
}
get inputs () {
return this._inputs;
}
inputParamTypes () {
return this._inputs.map((input) => input.kind);
}
encodeCall (tokens) {
return Encoder.encode(tokens);
}
}

View File

@@ -0,0 +1,52 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Constructor from './constructor';
import Param from './param';
import Token from '../token';
describe('abi/spec/Constructor', () => {
const inputsArr = [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }];
const bool = new Param('boolin', 'bool');
const string = new Param('stringin', 'string');
const inputs = [bool, string];
const cr = new Constructor({ inputs: inputsArr });
describe('constructor', () => {
it('stores the inputs as received', () => {
expect(cr.inputs).to.deep.equal(inputs);
});
it('matches empty inputs with []', () => {
expect(new Constructor({}).inputs).to.deep.equal([]);
});
});
describe('inputParamTypes', () => {
it('retrieves the input types as received', () => {
expect(cr.inputParamTypes()).to.deep.equal([bool.kind, string.kind]);
});
});
describe('encodeCall', () => {
it('encodes correctly', () => {
const result = cr.encodeCall([new Token('bool', true), new Token('string', 'jacogr')]);
expect(result).to.equal('0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066a61636f67720000000000000000000000000000000000000000000000000000');
});
});
});

View File

@@ -0,0 +1,30 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default class DecodedLog {
constructor (params, address) {
this._params = params;
this._address = address;
}
get address () {
return this._address;
}
get params () {
return this._params;
}
}

View File

@@ -0,0 +1,28 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import DecodedLog from './decodedLog';
const log = new DecodedLog('someParams', 'someAddress');
describe('abi/spec/event/DecodedLog', () => {
describe('constructor', () => {
it('sets internal state', () => {
expect(log.params).to.equal('someParams');
expect(log.address).to.equal('someAddress');
});
});
});

View File

@@ -0,0 +1,45 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import ParamType from '../paramType/paramType';
import Token from '../../token/token';
import { isInstanceOf } from '../../util/types';
export default class DecodedLogParam {
constructor (name, kind, token) {
if (!isInstanceOf(kind, ParamType)) {
throw new Error('kind not instanceof ParamType');
} else if (!isInstanceOf(token, Token)) {
throw new Error('token not instanceof Token');
}
this._name = name;
this._kind = kind;
this._token = token;
}
get name () {
return this._name;
}
get kind () {
return this._kind;
}
get token () {
return this._token;
}
}

View File

@@ -0,0 +1,42 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import DecodedLogParam from './decodedLogParam';
import ParamType from '../paramType';
import Token from '../../token';
describe('abi/spec/event/DecodedLogParam', () => {
describe('constructor', () => {
const pt = new ParamType('bool');
const tk = new Token('bool');
it('disallows kind not instanceof ParamType', () => {
expect(() => new DecodedLogParam('test', 'param')).to.throw(/ParamType/);
});
it('disallows token not instanceof Token', () => {
expect(() => new DecodedLogParam('test', pt, 'token')).to.throw(/Token/);
});
it('stores all parameters received', () => {
const log = new DecodedLogParam('test', pt, tk);
expect(log.name).to.equal('test');
expect(log.kind).to.equal(pt);
expect(log.token).to.equal(tk);
});
});
});

View File

@@ -0,0 +1,113 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Decoder from '../../decoder/decoder';
import DecodedLog from './decodedLog';
import DecodedLogParam from './decodedLogParam';
import EventParam from './eventParam';
import { asAddress } from '../../util/sliceAs';
import { eventSignature } from '../../util/signature';
export default class Event {
constructor (abi) {
this._name = abi.name;
this._inputs = EventParam.toEventParams(abi.inputs || []);
this._anonymous = !!abi.anonymous;
const { id, signature } = eventSignature(this._name, this.inputParamTypes());
this._id = id;
this._signature = signature;
}
get name () {
return this._name;
}
get id () {
return this._id;
}
get inputs () {
return this._inputs;
}
get anonymous () {
return this._anonymous;
}
get signature () {
return this._signature;
}
inputParamTypes () {
return this._inputs.map((input) => input.kind);
}
inputParamNames () {
return this._inputs.map((input) => input.name);
}
indexedParams (indexed) {
return this._inputs.filter((input) => input.indexed === indexed);
}
decodeLog (topics, data) {
const topicParams = this.indexedParams(true);
const dataParams = this.indexedParams(false);
let address;
let toSkip;
if (!this.anonymous) {
address = asAddress(topics[0]);
toSkip = 1;
} else {
toSkip = 0;
}
const topicTypes = topicParams.map((param) => param.kind);
const flatTopics = topics
.filter((topic, idx) => idx >= toSkip)
.map((topic) => {
return (topic.substr(0, 2) === '0x')
? topic.substr(2)
: topic;
}).join('');
const topicTokens = Decoder.decode(topicTypes, flatTopics);
if (topicTokens.length !== (topics.length - toSkip)) {
throw new Error('Invalid topic data');
}
const dataTypes = dataParams.map((param) => param.kind);
const dataTokens = Decoder.decode(dataTypes, data);
const namedTokens = {};
topicParams.forEach((param, idx) => {
namedTokens[param.name] = topicTokens[idx];
});
dataParams.forEach((param, idx) => {
namedTokens[param.name] = dataTokens[idx];
});
const inputParamTypes = this.inputParamTypes();
const decodedParams = this.inputParamNames()
.map((name, idx) => new DecodedLogParam(name, inputParamTypes[idx], namedTokens[name]));
return new DecodedLog(decodedParams, address);
}
}

View File

@@ -0,0 +1,111 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import Event from './event';
import EventParam from './eventParam';
import DecodedLogParam from './decodedLogParam';
import ParamType from '../paramType';
import Token from '../../token';
describe('abi/spec/event/Event', () => {
const inputArr = [{ name: 'a', type: 'bool' }, { name: 'b', type: 'uint', indexed: true }];
const inputs = [new EventParam('a', 'bool', false), new EventParam('b', 'uint', true)];
const event = new Event({ name: 'test', inputs: inputArr, anonymous: true });
describe('constructor', () => {
it('stores the parameters as received', () => {
expect(event.name).to.equal('test');
expect(event.inputs).to.deep.equal(inputs);
expect(event.anonymous).to.be.true;
});
it('matches empty inputs with []', () => {
expect(new Event({ name: 'test' }).inputs).to.deep.equal([]);
});
it('sets the event signature', () => {
expect(new Event({ name: 'baz' }).signature)
.to.equal('a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf');
});
});
describe('inputParamTypes', () => {
it('returns all the types', () => {
expect(event.inputParamTypes()).to.deep.equal([new ParamType('bool'), new ParamType('uint', null, 256, true)]);
});
});
describe('inputParamNames', () => {
it('returns all the names', () => {
expect(event.inputParamNames()).to.deep.equal(['a', 'b']);
});
});
describe('indexedParams', () => {
it('returns all indexed parameters (indexed)', () => {
expect(event.indexedParams(true)).to.deep.equal([inputs[1]]);
});
it('returns all indexed parameters (non-indexed)', () => {
expect(event.indexedParams(false)).to.deep.equal([inputs[0]]);
});
});
describe('decodeLog', () => {
it('decodes an event', () => {
const event = new Event({
name: 'foo',
inputs: [
{ name: 'a', type: 'int' },
{ name: 'b', type: 'int', indexed: true },
{ name: 'c', type: 'address' },
{ name: 'd', type: 'address', indexed: true }
]
});
const decoded = event.decodeLog([
'0000000000000000000000004444444444444444444444444444444444444444',
'0000000000000000000000000000000000000000000000000000000000000002',
'0000000000000000000000001111111111111111111111111111111111111111' ],
'00000000000000000000000000000000000000000000000000000000000000030000000000000000000000002222222222222222222222222222222222222222');
expect(decoded.address).to.equal('0x4444444444444444444444444444444444444444');
expect(decoded.params).to.deep.equal([
new DecodedLogParam('a', new ParamType('int', null, 256), new Token('int', new BigNumber(3))),
new DecodedLogParam('b', new ParamType('int', null, 256, true), new Token('int', new BigNumber(2))),
new DecodedLogParam('c', new ParamType('address'), new Token('address', '0x2222222222222222222222222222222222222222')),
new DecodedLogParam('d', new ParamType('address', null, 0, true), new Token('address', '0x1111111111111111111111111111111111111111'))
]);
});
it('decodes an anonymous event', () => {
const event = new Event({ name: 'foo', inputs: [{ name: 'a', type: 'int' }], anonymous: true });
const decoded = event.decodeLog([], '0000000000000000000000000000000000000000000000000000000000000003');
expect(decoded.address).to.not.be.ok;
expect(decoded.params).to.deep.equal([
new DecodedLogParam('a', new ParamType('int', null, 256), new Token('int', new BigNumber(3)))
]);
});
it('throws on invalid topics', () => {
const event = new Event({ name: 'foo', inputs: [{ name: 'a', type: 'int' }], anonymous: true });
expect(() => event.decodeLog(['0000000000000000000000004444444444444444444444444444444444444444'], '0000000000000000000000000000000000000000000000000000000000000003')).to.throw(/Invalid/);
});
});
});

View File

@@ -0,0 +1,41 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { toParamType } from '../paramType/format';
export default class EventParam {
constructor (name, type, indexed = false) {
this._name = name;
this._indexed = indexed;
this._kind = toParamType(type, indexed);
}
get name () {
return this._name;
}
get kind () {
return this._kind;
}
get indexed () {
return this._indexed;
}
static toEventParams (params) {
return params.map((param) => new EventParam(param.name, param.type, param.indexed));
}
}

View File

@@ -0,0 +1,43 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import EventParam from './eventParam';
describe('abi/spec/event/EventParam', () => {
describe('constructor', () => {
it('sets the properties', () => {
const param = new EventParam('foo', 'uint', true);
expect(param.name).to.equal('foo');
expect(param.kind.type).to.equal('uint');
expect(param.indexed).to.be.true;
});
it('uses defaults for indexed', () => {
expect(new EventParam('foo', 'uint').indexed).to.be.false;
});
});
describe('toEventParams', () => {
it('maps an array of params', () => {
const params = EventParam.toEventParams([{ name: 'foo', type: 'uint' }]);
expect(params.length).to.equal(1);
expect(params[0].indexed).to.be.false;
expect(params[0].name).to.equal('foo');
expect(params[0].kind.type).to.equal('uint');
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './event';

View File

@@ -0,0 +1,87 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Decoder from '../decoder/decoder';
import Encoder from '../encoder/encoder';
import Param from './param';
import { methodSignature } from '../util/signature';
export default class Func {
constructor (abi) {
this._abi = abi;
this._name = abi.name;
this._constant = !!abi.constant;
this._payable = abi.payable;
this._inputs = Param.toParams(abi.inputs || []);
this._outputs = Param.toParams(abi.outputs || []);
const { id, signature } = methodSignature(this._name, this.inputParamTypes());
this._id = id;
this._signature = signature;
}
get abi () {
return this._abi;
}
get constant () {
return this._constant;
}
get name () {
return this._name;
}
get id () {
return this._id;
}
get payable () {
return this._payable;
}
get inputs () {
return this._inputs;
}
get outputs () {
return this._outputs;
}
get signature () {
return this._signature;
}
inputParamTypes () {
return this._inputs.map((input) => input.kind);
}
outputParamTypes () {
return this._outputs.map((output) => output.kind);
}
encodeCall (tokens) {
return `${this._signature}${Encoder.encode(tokens)}`;
}
decodeInput (data) {
return Decoder.decode(this.inputParamTypes(), data);
}
decodeOutput (data) {
return Decoder.decode(this.outputParamTypes(), data);
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Func from './function';
import Param from './param';
import Token from '../token';
describe('abi/spec/Function', () => {
const inputsArr = [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }];
const outputsArr = [{ name: 'output', type: 'uint' }];
const uint = new Param('output', 'uint');
const bool = new Param('boolin', 'bool');
const string = new Param('stringin', 'string');
const inputs = [bool, string];
const outputs = [uint];
const func = new Func({
name: 'test',
inputs: inputsArr,
outputs: outputsArr
});
describe('constructor', () => {
it('stores the parameters as received', () => {
expect(func.name).to.equal('test');
expect(func.constant).to.be.false;
expect(func.inputs).to.deep.equal(inputs);
expect(func.outputs).to.deep.equal(outputs);
});
it('matches empty inputs with []', () => {
expect(new Func({ name: 'test', outputs: outputsArr }).inputs).to.deep.equal([]);
});
it('matches empty outputs with []', () => {
expect(new Func({ name: 'test', inputs: inputsArr }).outputs).to.deep.equal([]);
});
it('sets the method signature', () => {
expect(new Func({ name: 'baz' }).signature).to.equal('a7916fac');
});
it('allows constant functions', () => {
expect(new Func({ name: 'baz', constant: true }).constant).to.be.true;
});
});
describe('inputParamTypes', () => {
it('retrieves the input types as received', () => {
expect(func.inputParamTypes()).to.deep.equal([bool.kind, string.kind]);
});
});
describe('outputParamTypes', () => {
it('retrieves the output types as received', () => {
expect(func.outputParamTypes()).to.deep.equal([uint.kind]);
});
});
describe('encodeCall', () => {
it('encodes the call correctly', () => {
const result = func.encodeCall([new Token('bool', true), new Token('string', 'jacogr')]);
expect(result).to.equal('023562050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066a61636f67720000000000000000000000000000000000000000000000000000');
});
});
describe('decodeOutput', () => {
it('decodes the result correctly', () => {
const result = func.decodeOutput('1111111111111111111111111111111111111111111111111111111111111111');
expect(result[0].value.toString(16)).to.equal('1111111111111111111111111111111111111111111111111111111111111111');
});
});
});

17
js/src/abi/spec/index.js Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './interface';

View File

@@ -0,0 +1,73 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Constructor from './constructor';
import Event from './event/event';
import Func from './function';
import Token from '../token';
export default class Interface {
constructor (abi) {
this._interface = Interface.parseABI(abi);
}
get interface () {
return this._interface;
}
get constructors () {
return this._interface.filter((item) => item instanceof Constructor);
}
get events () {
return this._interface.filter((item) => item instanceof Event);
}
get functions () {
return this._interface.filter((item) => item instanceof Func);
}
encodeTokens (paramTypes, values) {
const createToken = function (paramType, value) {
if (paramType.subtype) {
return new Token(paramType.type, value.map((entry) => createToken(paramType.subtype, entry)));
}
return new Token(paramType.type, value);
};
return paramTypes.map((paramType, idx) => createToken(paramType, values[idx]));
}
static parseABI (abi) {
return abi.map((item) => {
switch (item.type) {
case 'constructor':
return new Constructor(item);
case 'event':
return new Event(item);
case 'function':
case 'fallback':
return new Func(item);
default:
throw new Error(`Unknown ABI type ${item.type}`);
}
});
}
}

View File

@@ -0,0 +1,126 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Interface from './interface';
import ParamType from './paramType';
import Token from '../token';
describe('abi/spec/Interface', () => {
const construct = {
type: 'constructor',
inputs: []
};
const event = {
type: 'event',
name: 'Event2',
anonymous: false,
inputs: [{ name: 'a', type: 'uint256', indexed: true }, { name: 'b', type: 'bytes32', indexed: false }]
};
const func = {
type: 'function',
name: 'foo',
inputs: [{ name: 'a', type: 'uint256' }],
outputs: []
};
describe('parseABI', () => {
it('throws on invalid types', () => {
expect(() => Interface.parseABI([{ type: 'noMatch' }])).to.throw(/noMatch/);
});
it('creates constructors', () => {
expect(Interface.parseABI([ construct ])).to.deep.equal([{ _inputs: [] }]);
});
it('creates events', () => {
expect(Interface.parseABI([ event ])[0].name).to.equal('Event2');
});
it('creates functions', () => {
expect(Interface.parseABI([ func ])[0].name).to.equal('foo');
});
it('parse complex interfaces', () => {
expect(Interface.parseABI([ construct, event, func ]).length).to.equal(3);
});
});
describe('constructor', () => {
const int = new Interface([ construct, event, func ]);
it('contains the full interface', () => {
expect(int.interface.length).to.equal(3);
});
it('contains the constructors', () => {
expect(int.constructors.length).to.equal(1);
});
it('contains the events', () => {
expect(int.events.length).to.equal(1);
});
it('contains the functions', () => {
expect(int.functions.length).to.equal(1);
});
});
describe('encodeTokens', () => {
const int = new Interface([ construct, event, func ]);
it('encodes simple types', () => {
expect(
int.encodeTokens(
[new ParamType('bool'), new ParamType('string'), new ParamType('int'), new ParamType('uint')],
[true, 'gavofyork', -123, 123]
)
).to.deep.equal([
new Token('bool', true), new Token('string', 'gavofyork'), new Token('int', -123), new Token('uint', 123)
]);
});
it('encodes array', () => {
expect(
int.encodeTokens(
[new ParamType('array', new ParamType('bool'))],
[[true, false, true]]
)
).to.deep.equal([
new Token('array', [
new Token('bool', true), new Token('bool', false), new Token('bool', true)
])
]);
});
it('encodes simple with array of array', () => {
expect(
int.encodeTokens(
[
new ParamType('bool'),
new ParamType('fixedArray', new ParamType('array', new ParamType('uint')), 2)
],
[true, [[0, 1], [2, 3]]]
)
).to.deep.equal([
new Token('bool', true),
new Token('fixedArray', [
new Token('array', [new Token('uint', 0), new Token('uint', 1)]),
new Token('array', [new Token('uint', 2), new Token('uint', 3)])
])
]);
});
});
});

36
js/src/abi/spec/param.js Normal file
View File

@@ -0,0 +1,36 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { toParamType } from './paramType/format';
export default class Param {
constructor (name, type) {
this._name = name;
this._kind = toParamType(type);
}
get name () {
return this._name;
}
get kind () {
return this._kind;
}
static toParams (params) {
return params.map((param) => new Param(param.name, param.type));
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Param from './param';
describe('abi/spec/Param', () => {
describe('constructor', () => {
const param = new Param('foo', 'uint');
it('sets the properties', () => {
expect(param.name).to.equal('foo');
expect(param.kind.type).to.equal('uint');
});
});
describe('toParams', () => {
it('maps an array of params', () => {
const params = Param.toParams([{ name: 'foo', type: 'uint' }]);
expect(params.length).to.equal(1);
expect(params[0].name).to.equal('foo');
expect(params[0].kind.type).to.equal('uint');
});
});
});

View File

@@ -0,0 +1,80 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import ParamType from './paramType';
export function toParamType (type, indexed) {
if (type[type.length - 1] === ']') {
const last = type.lastIndexOf('[');
const length = type.substr(last + 1, type.length - last - 2);
const subtype = toParamType(type.substr(0, last));
if (length.length === 0) {
return new ParamType('array', subtype, 0, indexed);
}
return new ParamType('fixedArray', subtype, parseInt(length, 10), indexed);
}
switch (type) {
case 'address':
case 'bool':
case 'bytes':
case 'string':
return new ParamType(type, null, 0, indexed);
case 'int':
case 'uint':
return new ParamType(type, null, 256, indexed);
default:
if (type.indexOf('uint') === 0) {
return new ParamType('uint', null, parseInt(type.substr(4), 10), indexed);
} else if (type.indexOf('int') === 0) {
return new ParamType('int', null, parseInt(type.substr(3), 10), indexed);
} else if (type.indexOf('bytes') === 0) {
return new ParamType('fixedBytes', null, parseInt(type.substr(5), 10), indexed);
}
throw new Error(`Cannot convert ${type} to valid ParamType`);
}
}
export function fromParamType (paramType) {
switch (paramType.type) {
case 'address':
case 'bool':
case 'bytes':
case 'string':
return paramType.type;
case 'int':
case 'uint':
return `${paramType.type}${paramType.length}`;
case 'fixedBytes':
return `bytes${paramType.length}`;
case 'fixedArray':
return `${fromParamType(paramType.subtype)}[${paramType.length}]`;
case 'array':
return `${fromParamType(paramType.subtype)}[]`;
default:
throw new Error(`Cannot convert from ParamType ${paramType.type}`);
}
}

View File

@@ -0,0 +1,228 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import ParamType from './paramType';
import { fromParamType, toParamType } from './format';
describe('abi/spec/paramType/format', () => {
describe('fromParamType', () => {
it('errors on invalid types', () => {
expect(() => fromParamType({ type: 'noMatch' })).to.throw(/noMatch/);
});
describe('simple types', () => {
it('converts address to address', () => {
const pt = new ParamType('address');
expect(fromParamType(pt)).to.equal('address');
});
it('converts bool to bool', () => {
const pt = new ParamType('bool');
expect(fromParamType(pt)).to.equal('bool');
});
it('converts bytes to bytes', () => {
const pt = new ParamType('bytes');
expect(fromParamType(pt)).to.equal('bytes');
});
it('converts string to string', () => {
const pt = new ParamType('string');
expect(fromParamType(pt)).to.equal('string');
});
});
describe('length types', () => {
it('converts int32 to int32', () => {
const pt = new ParamType('int', null, 32);
expect(fromParamType(pt)).to.equal('int32');
});
it('converts uint64 to int64', () => {
const pt = new ParamType('uint', null, 64);
expect(fromParamType(pt)).to.equal('uint64');
});
it('converts fixedBytes8 to bytes8', () => {
const pt = new ParamType('fixedBytes', null, 8);
expect(fromParamType(pt)).to.equal('bytes8');
});
});
describe('arrays', () => {
it('converts string[2] to string[2]', () => {
const pt = new ParamType('fixedArray', new ParamType('string'), 2);
expect(fromParamType(pt)).to.equal('string[2]');
});
it('converts bool[] to bool[]', () => {
const pt = new ParamType('array', new ParamType('bool'));
expect(fromParamType(pt)).to.equal('bool[]');
});
it('converts bool[][2] to bool[][2]', () => {
const pt = new ParamType('fixedArray', new ParamType('array', new ParamType('bool')), 2);
expect(fromParamType(pt)).to.equal('bool[][2]');
});
it('converts bool[2][] to bool[2][]', () => {
const pt = new ParamType('array', new ParamType('fixedArray', new ParamType('bool'), 2));
expect(fromParamType(pt)).to.equal('bool[2][]');
});
});
});
describe('toParamType', () => {
it('errors on invalid types', () => {
expect(() => toParamType('noMatch')).to.throw(/noMatch/);
});
describe('simple mapping', () => {
it('converts address to address', () => {
const pt = toParamType('address');
expect(pt.type).to.equal('address');
});
it('converts bool to bool', () => {
const pt = toParamType('bool');
expect(pt.type).to.equal('bool');
});
it('converts bytes to bytes', () => {
const pt = toParamType('bytes');
expect(pt.type).to.equal('bytes');
});
it('converts string to string', () => {
const pt = toParamType('string');
expect(pt.type).to.equal('string');
});
});
describe('number', () => {
it('converts int to int256', () => {
const pt = toParamType('int');
expect(pt.type).to.equal('int');
expect(pt.length).to.equal(256);
});
it('converts uint to uint256', () => {
const pt = toParamType('uint');
expect(pt.type).to.equal('uint');
expect(pt.length).to.equal(256);
});
});
describe('sized types', () => {
it('converts int32 to int32', () => {
const pt = toParamType('int32');
expect(pt.type).to.equal('int');
expect(pt.length).to.equal(32);
});
it('converts uint16 to uint16', () => {
const pt = toParamType('uint32');
expect(pt.type).to.equal('uint');
expect(pt.length).to.equal(32);
});
it('converts bytes8 to fixedBytes8', () => {
const pt = toParamType('bytes8');
expect(pt.type).to.equal('fixedBytes');
expect(pt.length).to.equal(8);
});
});
describe('arrays', () => {
describe('fixed arrays', () => {
it('creates fixed array', () => {
const pt = toParamType('bytes[8]');
expect(pt.type).to.equal('fixedArray');
expect(pt.subtype.type).to.equal('bytes');
expect(pt.length).to.equal(8);
});
it('creates fixed arrays of fixed arrays', () => {
const pt = toParamType('bytes[45][3]');
expect(pt.type).to.equal('fixedArray');
expect(pt.length).to.equal(3);
expect(pt.subtype.type).to.equal('fixedArray');
expect(pt.subtype.length).to.equal(45);
expect(pt.subtype.subtype.type).to.equal('bytes');
});
});
describe('dynamic arrays', () => {
it('creates a dynamic array', () => {
const pt = toParamType('bytes[]');
expect(pt.type).to.equal('array');
expect(pt.subtype.type).to.equal('bytes');
});
it('creates a dynamic array of dynamic arrays', () => {
const pt = toParamType('bool[][]');
expect(pt.type).to.equal('array');
expect(pt.subtype.type).to.equal('array');
expect(pt.subtype.subtype.type).to.equal('bool');
});
});
describe('mixed arrays', () => {
it('creates a fixed dynamic array', () => {
const pt = toParamType('bool[][3]');
expect(pt.type).to.equal('fixedArray');
expect(pt.length).to.equal(3);
expect(pt.subtype.type).to.equal('array');
expect(pt.subtype.subtype.type).to.equal('bool');
});
it('creates a dynamic fixed array', () => {
const pt = toParamType('bool[3][]');
expect(pt.type).to.equal('array');
expect(pt.subtype.type).to.equal('fixedArray');
expect(pt.subtype.length).to.equal(3);
expect(pt.subtype.subtype.type).to.equal('bool');
});
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './paramType';

View File

@@ -0,0 +1,52 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import TYPES from './types';
export default class ParamType {
constructor (type, subtype = null, length = 0, indexed = false) {
ParamType.validateType(type);
this._type = type;
this._subtype = subtype;
this._length = length;
this._indexed = indexed;
}
get type () {
return this._type;
}
get subtype () {
return this._subtype;
}
get length () {
return this._length;
}
get indexed () {
return this._indexed;
}
static validateType (type) {
if (TYPES.filter((_type) => type === _type).length) {
return true;
}
throw new Error(`Invalid type ${type} received for ParamType`);
}
}

View File

@@ -0,0 +1,87 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import ParamType from './paramType';
describe('abi/spec/paramType/ParamType', () => {
describe('validateType', () => {
it('validates address', () => {
expect(ParamType.validateType('address')).to.be.true;
});
it('validates fixedArray', () => {
expect(ParamType.validateType('fixedArray')).to.be.true;
});
it('validates array', () => {
expect(ParamType.validateType('array')).to.be.true;
});
it('validates fixedBytes', () => {
expect(ParamType.validateType('fixedBytes')).to.be.true;
});
it('validates bytes', () => {
expect(ParamType.validateType('bytes')).to.be.true;
});
it('validates bool', () => {
expect(ParamType.validateType('bool')).to.be.true;
});
it('validates int', () => {
expect(ParamType.validateType('int')).to.be.true;
});
it('validates uint', () => {
expect(ParamType.validateType('uint')).to.be.true;
});
it('validates string', () => {
expect(ParamType.validateType('string')).to.be.true;
});
it('throws an error on invalid types', () => {
expect(() => ParamType.validateType('noMatch')).to.throw(/noMatch/);
});
});
describe('constructor', () => {
it('throws an error on invalid types', () => {
expect(() => new ParamType('noMatch')).to.throw(/noMatch/);
});
it('sets the type of the object', () => {
expect((new ParamType('bool', null, 1)).type).to.equal('bool');
});
it('sets the subtype of the object', () => {
expect((new ParamType('array', 'bool', 1)).subtype).to.equal('bool');
});
it('sets the length of the object', () => {
expect((new ParamType('array', 'bool', 1)).length).to.equal(1);
});
it('sets the index of the object', () => {
expect((new ParamType('array', 'bool', 1, true)).indexed).to.be.true;
});
it('sets default values where none supplied', () => {
expect(Object.values(new ParamType('string'))).to.deep.equal(['string', null, 0, false]);
});
});
});

View File

@@ -0,0 +1,19 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const TYPES = ['address', 'bytes', 'int', 'uint', 'bool', 'string', 'array', 'fixedBytes', 'fixedArray'];
export default TYPES;

17
js/src/abi/token/index.js Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './token';

42
js/src/abi/token/token.js Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import TYPES from '../spec/paramType/types';
export default class Token {
constructor (type, value) {
Token.validateType(type);
this._type = type;
this._value = value;
}
get type () {
return this._type;
}
get value () {
return this._value;
}
static validateType (type) {
if (TYPES.filter((_type) => type === _type).length) {
return true;
}
throw new Error(`Invalid type ${type} received for Token`);
}
}

View File

@@ -0,0 +1,75 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Token from './token';
describe('abi/token/token', () => {
describe('validateType', () => {
it('validates address', () => {
expect(Token.validateType('address')).to.be.true;
});
it('validates fixedArray', () => {
expect(Token.validateType('fixedArray')).to.be.true;
});
it('validates array', () => {
expect(Token.validateType('array')).to.be.true;
});
it('validates fixedBytes', () => {
expect(Token.validateType('fixedBytes')).to.be.true;
});
it('validates bytes', () => {
expect(Token.validateType('bytes')).to.be.true;
});
it('validates bool', () => {
expect(Token.validateType('bool')).to.be.true;
});
it('validates int', () => {
expect(Token.validateType('int')).to.be.true;
});
it('validates uint', () => {
expect(Token.validateType('uint')).to.be.true;
});
it('validates string', () => {
expect(Token.validateType('string')).to.be.true;
});
it('throws an error on invalid types', () => {
expect(() => Token.validateType('noMatch')).to.throw(/noMatch/);
});
});
describe('constructor', () => {
it('throws an error on invalid types', () => {
expect(() => new Token('noMatch', '1')).to.throw(/noMatch/);
});
it('sets the type of the object', () => {
expect((new Token('bool', '1')).type).to.equal('bool');
});
it('sets the value of the object', () => {
expect((new Token('bool', '1')).value).to.equal('1');
});
});
});

View File

@@ -0,0 +1,65 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase
export function isChecksumValid (_address) {
const address = _address.replace('0x', '');
const hash = keccak_256(address.toLowerCase(address));
for (let n = 0; n < 40; n++) {
const hashval = parseInt(hash[n], 16);
const isLower = address[n].toUpperCase() !== address[n];
const isUpper = address[n].toLowerCase() !== address[n];
if ((hashval > 7 && isLower) || (hashval <= 7 && isUpper)) {
return false;
}
}
return true;
}
export function isAddress (address) {
if (address && address.length === 42) {
if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
return false;
} else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) {
return true;
}
return isChecksumValid(address);
}
return false;
}
export function toChecksumAddress (_address) {
const address = (_address || '').toLowerCase();
if (!isAddress(address)) {
return '';
}
const hash = keccak_256(address.slice(-40));
let result = '0x';
for (let n = 0; n < 40; n++) {
result = `${result}${parseInt(hash[n], 16) > 7 ? address[n + 2].toUpperCase() : address[n + 2]}`;
}
return result;
}

View File

@@ -0,0 +1,100 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { isChecksumValid, isAddress, toChecksumAddress } from './address';
describe('abi/util/address', () => {
const value = '63Cf90D3f0410092FC0fca41846f596223979195';
const address = `0x${value}`;
const lowercase = `0x${value.toLowerCase()}`;
const uppercase = `0x${value.toUpperCase()}`;
const invalid = '0x' + value.split('').map((char) => {
if (char >= 'a' && char <= 'f') {
return char.toUpperCase();
} else if (char >= 'A' && char <= 'F') {
return char.toLowerCase();
}
return char;
}).join('');
const invalidhex = '0x01234567890123456789012345678901234567gh';
describe('isChecksumValid', () => {
it('returns false when fully lowercase', () => {
expect(isChecksumValid(lowercase)).to.be.false;
});
it('returns false when fully uppercase', () => {
expect(isChecksumValid(uppercase)).to.be.false;
});
it('returns false on a mixed-case address', () => {
expect(isChecksumValid(invalid)).to.be.false;
});
it('returns true on a checksummed address', () => {
expect(isChecksumValid(address)).to.be.true;
});
});
describe('isAddress', () => {
it('returns true when fully lowercase', () => {
expect(isAddress(lowercase)).to.be.true;
});
it('returns true when fully uppercase', () => {
expect(isAddress(uppercase)).to.be.true;
});
it('returns true when checksummed', () => {
expect(isAddress(address)).to.be.true;
});
it('returns false when invalid checksum', () => {
expect(isAddress(invalid)).to.be.false;
});
it('returns false on valid length, non-hex', () => {
expect(isAddress(invalidhex)).to.be.false;
});
});
describe('toChecksumAddress', () => {
it('returns empty when no address specified', () => {
expect(toChecksumAddress()).to.equal('');
});
it('returns empty on invalid address structure', () => {
expect(toChecksumAddress('0xnotaddress')).to.equal('');
});
it('returns formatted address on checksum input', () => {
expect(toChecksumAddress(address)).to.equal(address);
});
it('returns formatted address on lowercase input', () => {
expect(toChecksumAddress(lowercase)).to.equal(address);
});
it('returns formatted address on uppercase input', () => {
expect(toChecksumAddress(uppercase)).to.equal(address);
});
it('returns formatted address on mixed input', () => {
expect(toChecksumAddress(invalid)).to.equal(address);
});
});
});

75
js/src/abi/util/pad.js Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import utf8 from 'utf8';
import { isArray } from './types';
const ZERO_64 = '0000000000000000000000000000000000000000000000000000000000000000';
export function padAddress (_input) {
const input = _input.substr(0, 2) === '0x' ? _input.substr(2) : _input;
return `${ZERO_64}${input}`.slice(-64);
}
export function padBool (input) {
return `${ZERO_64}${input ? '1' : '0'}`.slice(-64);
}
export function padU32 (input) {
let bn = new BigNumber(input);
if (bn.lessThan(0)) {
bn = new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)
.plus(bn).plus(1);
}
return `${ZERO_64}${bn.toString(16)}`.slice(-64);
}
function stringToBytes (input) {
if (isArray(input)) {
return input;
} else if (input.substr(0, 2) === '0x') {
return input.substr(2).toLowerCase().match(/.{1,2}/g).map((value) => parseInt(value, 16));
} else {
return input.split('').map((char) => char.charCodeAt(0));
}
}
export function padBytes (_input) {
const input = stringToBytes(_input);
return `${padU32(input.length)}${padFixedBytes(input)}`;
}
export function padFixedBytes (_input) {
const input = stringToBytes(_input);
const sinput = input.map((code) => `0${code.toString(16)}`.slice(-2)).join('');
const max = Math.floor((sinput.length + 63) / 64) * 64;
return `${sinput}${ZERO_64}`.substr(0, max);
}
export function padString (input) {
const array = utf8.encode(input)
.split('')
.map((char) => char.charCodeAt(0));
return padBytes(array);
}

124
js/src/abi/util/pad.spec.js Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { padAddress, padBool, padBytes, padFixedBytes, padString, padU32 } from './pad';
describe('abi/util/pad', () => {
const SHORT15 = '1234567890abcdef';
const BYTES15 = [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef];
const LONG15 = `${SHORT15}000000000000000000000000000000000000000000000000`;
const PAD123 = '0000000000000000000000000000000000000000000000000000000000000123';
describe('padAddress', () => {
it('pads to 64 characters', () => {
expect(padAddress('123')).to.equal(PAD123);
});
it('strips leading 0x when passed in', () => {
expect(padFixedBytes(`0x${PAD123}`)).to.equal(PAD123);
});
});
describe('padBool', () => {
const TRUE = '0000000000000000000000000000000000000000000000000000000000000001';
const FALSE = '0000000000000000000000000000000000000000000000000000000000000000';
it('pads true to 64 characters', () => {
expect(padBool(true)).to.equal(TRUE);
});
it('pads false to 64 characters', () => {
expect(padBool(false)).to.equal(FALSE);
});
});
describe('padU32', () => {
it('left pads length < 64 bytes to 64 bytes', () => {
expect(padU32(1)).to.equal('0000000000000000000000000000000000000000000000000000000000000001');
});
it('pads hex representation', () => {
expect(padU32(0x123)).to.equal(PAD123);
});
it('pads decimal representation', () => {
expect(padU32(291)).to.equal(PAD123);
});
it('pads string representation', () => {
expect(padU32('0x123')).to.equal(PAD123);
});
it('pads BigNumber representation', () => {
expect(padU32(new BigNumber(0x123))).to.equal(PAD123);
});
it('converts negative numbers to 2s complement', () => {
expect(padU32(-123)).to.equal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85');
});
});
describe('padFixedBytes', () => {
it('right pads length < 64 bytes to 64 bytes (string)', () => {
expect(padFixedBytes(`0x${SHORT15}`)).to.equal(LONG15);
});
it('right pads length < 64 bytes to 64 bytes (array)', () => {
expect(padFixedBytes(BYTES15)).to.equal(LONG15);
});
it('right pads length > 64 bytes (64 byte multiples)', () => {
expect(padFixedBytes(`0x${LONG15}${SHORT15}`)).to.equal(`${LONG15}${LONG15}`);
});
it('strips leading 0x when passed in', () => {
expect(padFixedBytes(`0x${SHORT15}`)).to.equal(LONG15);
});
});
describe('padBytes', () => {
it('right pads length < 64, adding the length (string)', () => {
const result = padBytes(`0x${SHORT15}`);
expect(result.length).to.equal(128);
expect(result).to.equal(`${padU32(8)}${LONG15}`);
});
it('right pads length < 64, adding the length (array)', () => {
const result = padBytes(BYTES15);
expect(result.length).to.equal(128);
expect(result).to.equal(`${padU32(8)}${LONG15}`);
});
it('right pads length > 64, adding the length', () => {
const result = padBytes(`0x${LONG15}${SHORT15}`);
expect(result.length).to.equal(192);
expect(result).to.equal(`${padU32(0x28)}${LONG15}${LONG15}`);
});
});
describe('padString', () => {
it('correctly converts & pads strings', () => {
const result = padString('gavofyork');
expect(result.length).to.equal(128);
expect(result).to.equal(padBytes('0x6761766f66796f726b'));
});
});
});

View File

@@ -0,0 +1,31 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase
import { fromParamType } from '../spec/paramType/format';
export function eventSignature (name, params) {
const types = (params || []).map(fromParamType).join(',');
const id = `${name || ''}(${types})`;
return { id, signature: keccak_256(id) };
}
export function methodSignature (name, params) {
const { id, signature } = eventSignature(name, params);
return { id, signature: signature.substr(0, 8) };
}

View File

@@ -0,0 +1,68 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { eventSignature, methodSignature } from './signature';
describe('abi/util/signature', () => {
describe('eventSignature', () => {
it('encodes signature baz() correctly', () => {
expect(eventSignature('baz', []))
.to.deep.equal({ id: 'baz()', signature: 'a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf' });
});
it('encodes signature baz(uint32) correctly', () => {
expect(eventSignature('baz', [{ type: 'uint', length: 32 }]))
.to.deep.equal({ id: 'baz(uint32)', signature: '7d68785e8fc871be024b75964bd86d093511d4bc2dc7cf7bea32c48a0efaecb1' });
});
it('encodes signature baz(uint32, bool) correctly', () => {
expect(eventSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }]))
.to.deep.equal({ id: 'baz(uint32,bool)', signature: 'cdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2' });
});
it('encodes no-name signature correctly as ()', () => {
expect(eventSignature(undefined, []))
.to.deep.equal({ id: '()', signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' });
});
it('encodes no-params signature correctly as ()', () => {
expect(eventSignature(undefined, undefined))
.to.deep.equal({ id: '()', signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' });
});
});
describe('methodSignature', () => {
it('encodes signature baz() correctly', () => {
expect(methodSignature('baz', [])).to.deep.equal({ id: 'baz()', signature: 'a7916fac' });
});
it('encodes signature baz(uint32) correctly', () => {
expect(methodSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({ id: 'baz(uint32)', signature: '7d68785e' });
});
it('encodes signature baz(uint32, bool) correctly', () => {
expect(methodSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({ id: 'baz(uint32,bool)', signature: 'cdcd77c0' });
});
it('encodes no-name signature correctly as ()', () => {
expect(methodSignature(undefined, [])).to.deep.equal({ id: '()', signature: '861731d5' });
});
it('encodes no-params signature correctly as ()', () => {
expect(methodSignature(undefined, undefined)).to.deep.equal({ id: '()', signature: '861731d5' });
});
});
});

35
js/src/abi/util/slice.js Normal file
View File

@@ -0,0 +1,35 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { padAddress } from './pad';
export function sliceData (_data) {
if (!_data || !_data.length) {
return [];
}
let data = (_data.substr(0, 2) === '0x') ? _data.substr(2) : _data;
if (!data.length) {
data = padAddress('');
}
if (data.length % 64) {
throw new Error(`Invalid data length (not mod 64) passed to sliceData, ${data}, % 64 == ${data.length % 64}`);
}
return data.match(/.{1,64}/g);
}

View File

@@ -0,0 +1,48 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { sliceData } from './slice';
describe('abi/util/slice', () => {
describe('sliceData', () => {
const slice1 = '131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b';
const slice2 = '2124768576358735263578356373526387638357635873563586353756358763';
it('throws an error on mod 64 != 0', () => {
expect(() => sliceData('123')).to.throw(/sliceData/);
});
it('returns an empty array when length === 0', () => {
expect(sliceData('')).to.deep.equal([]);
});
it('returns an array with the slices otherwise', () => {
const sliced = sliceData(`${slice1}${slice2}`);
expect(sliced.length).to.equal(2);
expect(sliced[0]).to.equal(slice1);
expect(sliced[1]).to.equal(slice2);
});
it('removes leading 0x when passed in', () => {
const sliced = sliceData(`0x${slice1}${slice2}`);
expect(sliced.length).to.equal(2);
expect(sliced[0]).to.equal(slice1);
expect(sliced[1]).to.equal(slice2);
});
});
});

View File

@@ -0,0 +1,47 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { toChecksumAddress } from './address';
export function asU32 (slice) {
// TODO: validation
return new BigNumber(slice, 16);
}
export function asI32 (slice) {
if (new BigNumber(slice.substr(0, 1), 16).toString(2)[0] === '1') {
return new BigNumber(slice, 16)
.minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16))
.minus(1);
}
return new BigNumber(slice, 16);
}
export function asAddress (slice) {
// TODO: address validation?
return toChecksumAddress(`0x${slice.slice(-40)}`);
}
export function asBool (slice) {
// TODO: everything else should be 0
return new BigNumber(slice[63]).eq(1);
}

View File

@@ -0,0 +1,54 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { asAddress, asBool, asI32, asU32 } from './sliceAs';
describe('abi/util/sliceAs', () => {
const MAX_INT = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
describe('asAddress', () => {
it('correctly returns the last 0x40 characters', () => {
const address = '1111111111222222222233333333334444444444';
expect(asAddress(`000000000000000000000000${address}`)).to.equal(`0x${address}`);
});
});
describe('asBool', () => {
it('correctly returns true', () => {
expect(asBool('0000000000000000000000000000000000000000000000000000000000000001')).to.be.true;
});
it('correctly returns false', () => {
expect(asBool('0000000000000000000000000000000000000000000000000000000000000000')).to.be.false;
});
});
describe('asI32', () => {
it('correctly decodes positive numbers', () => {
expect(asI32('000000000000000000000000000000000000000000000000000000000000007b').toString()).to.equal('123');
});
it('correctly decodes negative numbers', () => {
expect(asI32('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85').toString()).to.equal('-123');
});
});
describe('asU32', () => {
it('returns a maxium U32', () => {
expect(asU32(MAX_INT).toString(16)).to.equal(MAX_INT);
});
});
});

27
js/src/abi/util/types.js Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export function isArray (test) {
return Object.prototype.toString.call(test) === '[object Array]';
}
export function isString (test) {
return Object.prototype.toString.call(test) === '[object String]';
}
export function isInstanceOf (test, clazz) {
return test instanceof clazz;
}

View File

@@ -0,0 +1,62 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { isArray, isString, isInstanceOf } from './types';
import Token from '../token';
describe('abi/util/types', () => {
describe('isArray', () => {
it('correctly identifies empty arrays as Array', () => {
expect(isArray([])).to.be.true;
});
it('correctly identifies non-empty arrays as Array', () => {
expect(isArray([1, 2, 3])).to.be.true;
});
it('correctly identifies strings as non-Array', () => {
expect(isArray('not an array')).to.be.false;
});
it('correctly identifies objects as non-Array', () => {
expect(isArray({})).to.be.false;
});
});
describe('isString', () => {
it('correctly identifies empty string as string', () => {
expect(isString('')).to.be.true;
});
it('correctly identifies string as string', () => {
expect(isString('123')).to.be.true;
});
});
describe('isInstanceOf', () => {
it('correctly identifies build-in instanceof', () => {
expect(isInstanceOf(new String('123'), String)).to.be.true; // eslint-disable-line no-new-wrappers
});
it('correctly identifies own instanceof', () => {
expect(isInstanceOf(new Token('int', 123), Token)).to.be.true;
});
it('correctly reports false for own', () => {
expect(isInstanceOf({ type: 'int' }, Token)).to.be.false;
});
});
});

145
js/src/api/README.md Normal file
View File

@@ -0,0 +1,145 @@
# ethapi-js
A thin, fast, low-level Promise-based wrapper around the Ethereum APIs.
[![Build Status](https://travis-ci.org/jacogr/ethapi-js.svg?branch=master)](https://travis-ci.org/jacogr/ethapi-js)
[![Coverage Status](https://coveralls.io/repos/github/jacogr/ethapi-js/badge.svg?branch=master)](https://coveralls.io/github/jacogr/ethapi-js?branch=master)
[![Dependency Status](https://david-dm.org/jacogr/ethapi-js.svg)](https://david-dm.org/jacogr/ethapi-js)
[![devDependency Status](https://david-dm.org/jacogr/ethapi-js/dev-status.svg)](https://david-dm.org/jacogr/ethapi-js#info=devDependencies)
## contributing
Clone the repo and install dependencies via `npm install`. Tests can be executed via
- `npm run testOnce` (100% covered unit tests)
- `npm run testE2E` (E2E against a running RPC-enabled testnet Parity/Geth instance, `parity --testnet` and for WebScokets, `geth --testnet --ws --wsorigins '*' --rpc`)
- setting the environment `DEBUG=true` will display the RPC POST bodies and responses on E2E tests
## installation
Install the package with `npm install --save ethapi-js` from the [npm registry ethapi-js](https://www.npmjs.com/package/ethapi-js)
## usage
### initialisation
```javascript
// import the actual EthApi class
import EthApi from 'ethapi-js';
// do the setup
const transport = new EthApi.Transport.Http('http://localhost:8545'); // or .Ws('ws://localhost:8546')
const ethapi = new EthApi(transport);
```
You will require native Promises and fetch support (latest browsers only), they can be utilised by
```javascript
import 'isomorphic-fetch';
import es6Promise from 'es6-promise';
es6Promise.polyfill();
```
### making calls
perform a call
```javascript
ethapi.eth
.coinbase()
.then((coinbase) => {
console.log(`The coinbase is ${coinbase}`);
});
```
multiple promises
```javascript
Promise
.all([
ethapi.eth.coinbase(),
ethapi.net.listening()
])
.then(([coinbase, listening]) => {
// do stuff here
});
```
chaining promises
```javascript
ethapi.eth
.newFilter({...})
.then((filterId) => ethapi.eth.getFilterChanges(filterId))
.then((changes) => {
console.log(changes);
});
```
### contracts
attach contract
```javascript
const abi = [{ name: 'callMe', inputs: [{ type: 'bool', ...}, { type: 'string', ...}]}, ...abi...];
const contract = new ethapi.newContract(abi);
```
deploy
```javascript
contract
.deploy('0xc0de', [params], 'superPassword')
.then((address) => {
console.log(`the contract was deployed at ${address}`);
});
```
attach a contract at address
```javascript
// via the constructor & .at function
const contract = api.newContract(abi).at('0xa9280...7347b');
// or on an already initialised contract
contract.at('0xa9280...7347b');
// perform calls here
```
find & call a function
```javascript
contract.named
.callMe
.call({ gas: 21000 }, [true, 'someString']) // or estimateGas or sendTransaction
.then((result) => {
console.log(`the result was ${result}`);
});
```
parse events from transaction receipt
```javascript
contract
.parseTransactionEvents(txReceipt)
.then((receipt) => {
receipt.logs.forEach((log) => {
console.log('log parameters', log.params);
});
});
```
## apis
APIs implement the calls as exposed in the [Ethcore JSON Ethereum RPC](https://github.com/ethcore/ethereum-rpc-json/) definitions. Mapping follows the naming conventions of the originals, i.e. `eth_call` becomes `eth.call`, `personal_accounts` becomes `personal.accounts`, etc.
- [ethapi.db](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#db)
- [ethapi.eth](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#eth)
- [ethapi.ethcore](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#ethcore)
- [ethapi.net](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#net)
- [ethapi.personal](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#personal)
- [ethapi.shh](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#shh)
- [ethapi.trace](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#trace)
- [ethapi.web3](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#web3)
As a verification step, all exposed interfaces are tested for existing and pointing to the correct endpoints by using the generated interfaces from the above repo.

125
js/src/api/api.js Normal file
View File

@@ -0,0 +1,125 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { Http, Ws } from './transport';
import Contract from './contract';
import { Db, Eth, Ethcore, Net, Personal, Shh, Trace, Web3 } from './rpc';
import Subscriptions from './subscriptions';
import util from './util';
import { isFunction } from './util/types';
export default class Api {
constructor (transport) {
if (!transport || !isFunction(transport.execute)) {
throw new Error('EthApi needs transport with execute() function defined');
}
this._transport = transport;
this._db = new Db(transport);
this._eth = new Eth(transport);
this._ethcore = new Ethcore(transport);
this._net = new Net(transport);
this._personal = new Personal(transport);
this._shh = new Shh(transport);
this._trace = new Trace(transport);
this._web3 = new Web3(transport);
this._subscriptions = new Subscriptions(this);
}
get db () {
return this._db;
}
get eth () {
return this._eth;
}
get ethcore () {
return this._ethcore;
}
get net () {
return this._net;
}
get personal () {
return this._personal;
}
get shh () {
return this._shh;
}
get trace () {
return this._trace;
}
get transport () {
return this._transport;
}
get web3 () {
return this._web3;
}
get util () {
return util;
}
newContract (abi, address) {
return new Contract(this, abi).at(address);
}
subscribe (subscriptionName, callback) {
return this._subscriptions.subscribe(subscriptionName, callback);
}
unsubscribe (subscriptionName, subscriptionId) {
return this._subscriptions.unsubscribe(subscriptionName, subscriptionId);
}
pollMethod (method, input, validate) {
const [_group, endpoint] = method.split('_');
const group = `_${_group}`;
return new Promise((resolve, reject) => {
const timeout = () => {
this[group][endpoint](input)
.then((result) => {
if (validate ? validate(result) : result) {
resolve(result);
} else {
setTimeout(timeout, 500);
}
})
.catch((error) => {
console.error('pollMethod', error);
reject(error);
});
};
timeout();
});
}
static Transport = {
Http: Http,
Ws: Ws
}
}

47
js/src/api/api.spec.js Normal file
View File

@@ -0,0 +1,47 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { TEST_HTTP_URL, endpointTest } from '../../test/mockRpc';
import Api from './api';
import ethereumRpc from '../jsonrpc/';
describe('api/Api', () => {
describe('constructor', () => {
it('requires defined/non-null transport object', () => {
expect(() => new Api()).to.throw(/Api needs transport/);
expect(() => new Api(null)).to.throw(/Api needs transport/);
});
it('requires an execute function on the transport object', () => {
expect(() => new Api({})).to.throw(/Api needs transport/);
expect(() => new Api({ execute: true })).to.throw(/Api needs transport/);
});
});
describe('interface', () => {
const api = new Api(new Api.Transport.Http(TEST_HTTP_URL));
Object.keys(ethereumRpc).sort().forEach((endpoint) => {
describe(endpoint, () => {
Object.keys(ethereumRpc[endpoint]).sort().forEach((method) => {
endpointTest(api, endpoint, method);
});
});
});
});
});

View File

@@ -0,0 +1,319 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import Abi from '../../abi';
import Api from '../api';
import { isInstanceOf } from '../util/types';
let nextSubscriptionId = 0;
export default class Contract {
constructor (api, abi) {
if (!isInstanceOf(api, Api)) {
throw new Error('API instance needs to be provided to Contract');
} else if (!abi) {
throw new Error('ABI needs to be provided to Contract instance');
}
this._api = api;
this._abi = new Abi(abi);
this._subscriptions = {};
this._constructors = this._abi.constructors.map(this._bindFunction);
this._functions = this._abi.functions.map(this._bindFunction);
this._events = this._abi.events.map(this._bindEvent);
this._instance = {};
this._events.forEach((evt) => {
this._instance[evt.name] = evt;
});
this._functions.forEach((fn) => {
this._instance[fn.name] = fn;
});
this._sendSubscriptionChanges();
}
get address () {
return this._address;
}
get constructors () {
return this._constructors;
}
get events () {
return this._events;
}
get functions () {
return this._functions;
}
get instance () {
this._instance.address = this._address;
return this._instance;
}
get api () {
return this._api;
}
get abi () {
return this._abi;
}
at (address) {
this._address = address;
return this;
}
deploy (options, values, statecb) {
let gas;
const setState = (state) => {
if (!statecb) {
return;
}
return statecb(null, state);
};
setState({ state: 'estimateGas' });
return this._api.eth
.estimateGas(this._encodeOptions(this.constructors[0], options, values))
.then((_gas) => {
gas = _gas.mul(1.2);
options.gas = gas.toFixed(0);
setState({ state: 'postTransaction', gas });
return this._api.eth.postTransaction(this._encodeOptions(this.constructors[0], options, values));
})
.then((requestId) => {
setState({ state: 'checkRequest', requestId });
return this._pollCheckRequest(requestId);
})
.then((txhash) => {
setState({ state: 'getTransactionReceipt', txhash });
return this._pollTransactionReceipt(txhash, gas);
})
.then((receipt) => {
if (receipt.gasUsed.eq(gas)) {
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
}
setState({ state: 'hasReceipt', receipt });
this._address = receipt.contractAddress;
return this._address;
})
.then((address) => {
setState({ state: 'getCode' });
return this._api.eth.getCode(this._address);
})
.then((code) => {
if (code === '0x') {
throw new Error('Contract not deployed, getCode returned 0x');
}
setState({ state: 'completed' });
return this._address;
});
}
parseEventLogs (logs) {
return logs.map((log) => {
const signature = log.topics[0].substr(2);
const event = this.events.find((evt) => evt.signature === signature);
if (!event) {
throw new Error(`Unable to find event matching signature ${signature}`);
}
const decoded = event.decodeLog(log.topics, log.data);
log.params = {};
log.event = event.name;
decoded.params.forEach((param) => {
log.params[param.name] = param.token.value;
});
return log;
});
}
parseTransactionEvents (receipt) {
receipt.logs = this.parseEventLogs(receipt.logs);
return receipt;
}
_pollCheckRequest = (requestId) => {
return this._api.pollMethod('eth_checkRequest', requestId);
}
_pollTransactionReceipt = (txhash, gas) => {
return this.api.pollMethod('eth_getTransactionReceipt', txhash, (receipt) => {
if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) {
return false;
}
return true;
});
}
_encodeOptions (func, options, values) {
const tokens = func ? this._abi.encodeTokens(func.inputParamTypes(), values) : null;
const call = tokens ? func.encodeCall(tokens) : null;
if (options.data && options.data.substr(0, 2) === '0x') {
options.data = options.data.substr(2);
}
options.data = `0x${options.data || ''}${call || ''}`;
return options;
}
_addOptionsTo (options = {}) {
return Object.assign({
to: this._address
}, options);
}
_bindFunction = (func) => {
func.call = (options, values = []) => {
return this._api.eth
.call(this._encodeOptions(func, this._addOptionsTo(options), values))
.then((encoded) => func.decodeOutput(encoded))
.then((tokens) => tokens.map((token) => token.value))
.then((returns) => returns.length === 1 ? returns[0] : returns);
};
if (!func.constant) {
func.postTransaction = (options, values = []) => {
return this._api.eth
.postTransaction(this._encodeOptions(func, this._addOptionsTo(options), values));
};
func.estimateGas = (options, values = []) => {
return this._api.eth
.estimateGas(this._encodeOptions(func, this._addOptionsTo(options), values));
};
}
return func;
}
_bindEvent = (event) => {
event.subscribe = (options = {}, callback) => {
return this._subscribe(event, options, callback);
};
event.unsubscribe = (subscriptionId) => {
return this.unsubscribe(subscriptionId);
};
return event;
}
subscribe (eventName = null, options = {}, callback) {
return new Promise((resolve, reject) => {
let event = null;
if (eventName) {
event = this._events.find((evt) => evt.name === eventName);
if (!event) {
const events = this._events.map((evt) => evt.name).join(', ');
reject(new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`));
return;
}
}
return this._subscribe(event, options, callback).then(resolve).catch(reject);
});
}
_subscribe (event = null, _options, callback) {
const subscriptionId = nextSubscriptionId++;
const options = Object.assign({}, _options, {
address: this._address,
topics: [event ? event.signature : null]
});
return this._api.eth
.newFilter(options)
.then((filterId) => {
return this._api.eth
.getFilterLogs(filterId)
.then((logs) => {
callback(null, this.parseEventLogs(logs));
this._subscriptions[subscriptionId] = {
options,
callback,
filterId
};
return subscriptionId;
});
});
}
unsubscribe (subscriptionId) {
return this._api.eth
.uninstallFilter(this._subscriptions[subscriptionId].filterId)
.then(() => {
delete this._subscriptions[subscriptionId];
})
.catch((error) => {
console.error('unsubscribe', error);
});
}
_sendSubscriptionChanges = () => {
const subscriptions = Object.values(this._subscriptions);
const timeout = () => setTimeout(this._sendSubscriptionChanges, 1000);
Promise
.all(
subscriptions.map((subscription) => {
return this._api.eth.getFilterChanges(subscription.filterId);
})
)
.then((logsArray) => {
logsArray.forEach((logs, idx) => {
if (!logs || !logs.length) {
return;
}
try {
subscriptions[idx].callback(null, this.parseEventLogs(logs));
} catch (error) {
this.unsubscribe(idx);
console.error('_sendSubscriptionChanges', error);
}
});
timeout();
})
.catch((error) => {
console.error('_sendSubscriptionChanges', error);
timeout();
});
}
}

View File

@@ -0,0 +1,539 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import sinon from 'sinon';
import { TEST_HTTP_URL, mockHttp } from '../../../test/mockRpc';
import Abi from '../../abi';
import Api from '../api';
import Contract from './contract';
import { isInstanceOf, isFunction } from '../util/types';
const transport = new Api.Transport.Http(TEST_HTTP_URL);
const eth = new Api(transport);
describe('api/contract/Contract', () => {
const ADDR = '0x0123456789';
const ABI = [
{
type: 'function', name: 'test',
inputs: [{ name: 'boolin', type: 'bool' }, { name: 'stringin', type: 'string' }],
outputs: [{ type: 'uint' }]
},
{
type: 'function', name: 'test2',
outputs: [{ type: 'uint' }, { type: 'uint' }]
},
{ type: 'constructor' },
{ type: 'event', name: 'baz' },
{ type: 'event', name: 'foo' }
];
const VALUES = [true, 'jacogr'];
const ENCODED = '0x023562050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066a61636f67720000000000000000000000000000000000000000000000000000';
const RETURN1 = '0000000000000000000000000000000000000000000000000000000000123456';
const RETURN2 = '0000000000000000000000000000000000000000000000000000000000456789';
let scope;
describe('constructor', () => {
it('needs an EthAbi instance', () => {
expect(() => new Contract()).to.throw(/API instance needs to be provided to Contract/);
});
it('needs an ABI', () => {
expect(() => new Contract(eth)).to.throw(/ABI needs to be provided to Contract instance/);
});
describe('internal setup', () => {
const contract = new Contract(eth, ABI);
it('sets EthApi & parsed interface', () => {
expect(contract.address).to.not.be.ok;
expect(contract.api).to.deep.equal(eth);
expect(isInstanceOf(contract.abi, Abi)).to.be.ok;
});
it('attaches functions', () => {
expect(contract.functions.length).to.equal(2);
expect(contract.functions[0].name).to.equal('test');
});
it('attaches constructors', () => {
expect(contract.constructors.length).to.equal(1);
});
it('attaches events', () => {
expect(contract.events.length).to.equal(2);
expect(contract.events[0].name).to.equal('baz');
});
});
});
describe('at', () => {
it('sets returns the functions, events & sets the address', () => {
const contract = new Contract(eth, [
{
constant: true,
inputs: [{
name: '_who',
type: 'address'
}],
name: 'balanceOf',
outputs: [{
name: '',
type: 'uint256'
}],
type: 'function'
},
{
anonymous: false,
inputs: [{
indexed: false,
name: 'amount',
type: 'uint256'
}],
name: 'Drained',
type: 'event'
}
]);
contract.at('6789');
expect(Object.keys(contract.instance)).to.deep.equal(['Drained', 'balanceOf', 'address']);
expect(contract.address).to.equal('6789');
});
});
describe('parseTransactionEvents', () => {
it('checks for unmatched signatures', () => {
const contract = new Contract(eth, [{ anonymous: false, name: 'Message', type: 'event' }]);
expect(() => contract.parseTransactionEvents({
logs: [{
data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000',
topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
]
}]
})).to.throw(/event matching signature/);
});
it('parses a transaction log into the data', () => {
const contract = new Contract(eth, [
{
anonymous: false, name: 'Message', type: 'event',
inputs: [
{ indexed: true, name: 'postId', type: 'uint256' },
{ indexed: false, name: 'parentId', type: 'uint256' },
{ indexed: false, name: 'sender', type: 'address' },
{ indexed: false, name: 'at', type: 'uint256' },
{ indexed: false, name: 'messageId', type: 'uint256' },
{ indexed: false, name: 'message', type: 'string' }
]
}
]);
const decoded = contract.parseTransactionEvents({
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
blockNumber: '0x4fcd',
cumulativeGasUsed: '0xb57f',
gasUsed: '0xb57f',
logs: [{
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
blockNumber: '0x4fcd',
data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000',
logIndex: '0x0',
topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
],
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: '0x0'
}],
to: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: '0x0'
});
const log = decoded.logs[0];
expect(log.event).to.equal('Message');
expect(log.address).to.equal('0x22bff18ec62281850546a664bb63a5c06ac5f76c');
expect(log.params).to.deep.equal({
at: new BigNumber('1457965151'),
message: 'post(message)',
messageId: new BigNumber('281474976731085'),
parentId: new BigNumber(0),
postId: new BigNumber('281474976731104'),
sender: '0x63Cf90D3f0410092FC0fca41846f596223979195'
});
});
});
describe('_pollTransactionReceipt', () => {
const contract = new Contract(eth, ABI);
const ADDRESS = '0xD337e80eEdBdf86eDBba021797d7e4e00Bb78351';
const BLOCKNUMBER = '555000';
const RECEIPT = { contractAddress: ADDRESS.toLowerCase(), blockNumber: BLOCKNUMBER };
const EXPECT = { contractAddress: ADDRESS, blockNumber: new BigNumber(BLOCKNUMBER) };
let scope;
let receipt;
describe('success', () => {
before(() => {
scope = mockHttp([
{ method: 'eth_getTransactionReceipt', reply: { result: null } },
{ method: 'eth_getTransactionReceipt', reply: { result: null } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT } }
]);
return contract
._pollTransactionReceipt('0x123')
.then((_receipt) => {
receipt = _receipt;
});
});
it('sends multiple getTransactionReceipt calls', () => {
expect(scope.isDone()).to.be.true;
});
it('passes the txhash through', () => {
expect(scope.body.eth_getTransactionReceipt.params[0]).to.equal('0x123');
});
it('receives the final receipt', () => {
expect(receipt).to.deep.equal(EXPECT);
});
});
describe('error', () => {
before(() => {
scope = mockHttp([{ method: 'eth_getTransactionReceipt', reply: { error: { code: -1, message: 'failure' } } }]);
});
it('returns the errors', () => {
return contract
._pollTransactionReceipt('0x123')
.catch((error) => {
expect(error.message).to.match(/failure/);
});
});
});
});
describe('deploy', () => {
const contract = new Contract(eth, ABI);
const ADDRESS = '0xD337e80eEdBdf86eDBba021797d7e4e00Bb78351';
const RECEIPT_PEND = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 50, blockNumber: 0 };
const RECEIPT_DONE = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 50, blockNumber: 2500 };
const RECEIPT_EXCP = { contractAddress: ADDRESS.toLowerCase(), gasUsed: 1200, blockNumber: 2500 };
let scope;
describe('success', () => {
before(() => {
scope = mockHttp([
{ method: 'eth_estimateGas', reply: { result: 1000 } },
{ method: 'eth_postTransaction', reply: { result: '0x678' } },
{ method: 'eth_checkRequest', reply: { result: null } },
{ method: 'eth_checkRequest', reply: { result: '0x890' } },
{ method: 'eth_getTransactionReceipt', reply: { result: null } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_PEND } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } },
{ method: 'eth_getCode', reply: { result: '0x456' } }
]);
return contract.deploy({ data: '0x123' }, []);
});
it('calls estimateGas, postTransaction, checkRequest, getTransactionReceipt & getCode in order', () => {
expect(scope.isDone()).to.be.true;
});
it('passes the options through to postTransaction (incl. gas calculation)', () => {
expect(scope.body.eth_postTransaction.params).to.deep.equal([
{ data: '0x123', gas: '0x4b0' }
]);
});
it('sets the address of the contract', () => {
expect(contract.address).to.equal(ADDRESS);
});
});
describe('error', () => {
it('fails when gasUsed == gas', () => {
mockHttp([
{ method: 'eth_estimateGas', reply: { result: 1000 } },
{ method: 'eth_postTransaction', reply: { result: '0x678' } },
{ method: 'eth_checkRequest', reply: { result: '0x789' } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_EXCP } }
]);
return contract
.deploy({ data: '0x123' }, [])
.catch((error) => {
expect(error.message).to.match(/not deployed, gasUsed/);
});
});
it('fails when no code was deployed', () => {
mockHttp([
{ method: 'eth_estimateGas', reply: { result: 1000 } },
{ method: 'eth_postTransaction', reply: { result: '0x678' } },
{ method: 'eth_checkRequest', reply: { result: '0x789' } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } },
{ method: 'eth_getCode', reply: { result: '0x' } }
]);
return contract
.deploy({ data: '0x123' }, [])
.catch((error) => {
expect(error.message).to.match(/not deployed, getCode/);
});
});
});
});
describe('bindings', () => {
let contract;
let cons;
let func;
beforeEach(() => {
contract = new Contract(eth, ABI);
contract.at(ADDR);
cons = contract.constructors[0];
func = contract.functions.find((fn) => fn.name === 'test');
});
describe('_addOptionsTo', () => {
it('works on no object specified', () => {
expect(contract._addOptionsTo()).to.deep.equal({ to: ADDR });
});
it('uses the contract address when none specified', () => {
expect(contract._addOptionsTo({ from: 'me' })).to.deep.equal({ to: ADDR, from: 'me' });
});
it('overrides the contract address when specified', () => {
expect(contract._addOptionsTo({ to: 'you', from: 'me' })).to.deep.equal({ to: 'you', from: 'me' });
});
});
describe('attachments', () => {
it('attaches .call, .postTransaction & .estimateGas to constructors', () => {
expect(isFunction(cons.call)).to.be.true;
expect(isFunction(cons.postTransaction)).to.be.true;
expect(isFunction(cons.estimateGas)).to.be.true;
});
it('attaches .call, .postTransaction & .estimateGas to functions', () => {
expect(isFunction(func.call)).to.be.true;
expect(isFunction(func.postTransaction)).to.be.true;
expect(isFunction(func.estimateGas)).to.be.true;
});
it('attaches .call only to constant functions', () => {
func = (new Contract(eth, [{ type: 'function', name: 'test', constant: true }])).functions[0];
expect(isFunction(func.call)).to.be.true;
expect(isFunction(func.postTransaction)).to.be.false;
expect(isFunction(func.estimateGas)).to.be.false;
});
});
describe('postTransaction', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_postTransaction', reply: { result: ['hashId'] } }]);
});
it('encodes options and mades an eth_postTransaction call', () => {
return func
.postTransaction({ someExtras: 'foo' }, VALUES)
.then(() => {
expect(scope.isDone()).to.be.true;
expect(scope.body.eth_postTransaction.params[0]).to.deep.equal({
someExtras: 'foo',
to: ADDR,
data: ENCODED
});
});
});
});
describe('estimateGas', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_estimateGas', reply: { result: ['0x123'] } }]);
});
it('encodes options and mades an eth_estimateGas call', () => {
return func
.estimateGas({ someExtras: 'foo' }, VALUES)
.then((amount) => {
expect(scope.isDone()).to.be.true;
expect(amount.toString(16)).to.equal('123');
expect(scope.body.eth_estimateGas.params).to.deep.equal([{
someExtras: 'foo',
to: ADDR,
data: ENCODED
}]);
});
});
});
describe('call', () => {
it('encodes options and mades an eth_call call', () => {
scope = mockHttp([{ method: 'eth_call', reply: { result: RETURN1 } }]);
return func
.call({ someExtras: 'foo' }, VALUES)
.then((result) => {
expect(scope.isDone()).to.be.true;
expect(scope.body.eth_call.params).to.deep.equal([{
someExtras: 'foo',
to: ADDR,
data: ENCODED
}, 'latest']);
expect(result.toString(16)).to.equal('123456');
});
});
it('encodes options and mades an eth_call call (multiple returns)', () => {
scope = mockHttp([{ method: 'eth_call', reply: { result: `${RETURN1}${RETURN2}` } }]);
return contract.functions[1]
.call({}, [])
.then((result) => {
expect(scope.isDone()).to.be.true;
expect(result.length).to.equal(2);
expect(result[0].toString(16)).to.equal('123456');
expect(result[1].toString(16)).to.equal('456789');
});
});
});
});
describe('subscribe', () => {
const abi = [
{
anonymous: false, name: 'Message', type: 'event',
inputs: [
{ indexed: true, name: 'postId', type: 'uint256' },
{ indexed: false, name: 'parentId', type: 'uint256' },
{ indexed: false, name: 'sender', type: 'address' },
{ indexed: false, name: 'at', type: 'uint256' },
{ indexed: false, name: 'messageId', type: 'uint256' },
{ indexed: false, name: 'message', type: 'string' }
]
}
];
const logs = [{
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
blockNumber: '0x4fcd',
data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000',
logIndex: '0x0',
topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
],
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: '0x0'
}];
const parsed = [{
address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C',
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
blockNumber: new BigNumber(20429),
data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000',
event: 'Message',
logIndex: new BigNumber(0),
params: {
at: new BigNumber(1457965151),
message: 'post(message)',
messageId: new BigNumber(281474976731085),
parentId: new BigNumber(0),
postId: new BigNumber(281474976731104),
sender: '0x63Cf90D3f0410092FC0fca41846f596223979195'
},
topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', '0x0000000000000000000000000000000000000000000000000001000000004fe0'
],
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
transactionIndex: new BigNumber(0)
}];
let contract;
beforeEach(() => {
contract = new Contract(eth, abi);
contract.at(ADDR);
});
describe('invalid events', () => {
it('fails to subscribe to an invalid names', () => {
return contract
.subscribe('invalid')
.catch((error) => {
expect(error.message).to.match(/invalid is not a valid eventName/);
});
});
});
describe('valid events', () => {
let cbb;
let cbe;
beforeEach(() => {
scope = mockHttp([
{ method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } },
{ method: 'eth_newFilter', reply: { result: '0x123' } },
{ method: 'eth_getFilterLogs', reply: { result: logs } }
]);
cbb = sinon.stub();
cbe = sinon.stub();
return contract.subscribe('Message', {}, cbb);
});
it('sets the subscriptionId returned', () => {
return contract
.subscribe('Message', {}, cbe)
.then((subscriptionId) => {
expect(subscriptionId).to.equal(1);
});
});
it('creates a new filter and retrieves the logs on it', () => {
return contract
.subscribe('Message', {}, cbe)
.then((subscriptionId) => {
expect(scope.isDone()).to.be.true;
});
});
it('returns the logs to the callback', () => {
return contract
.subscribe('Message', {}, cbe)
.then((subscriptionId) => {
expect(cbe).to.have.been.calledWith(null, parsed);
});
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './contract';

139
js/src/api/format/input.js Normal file
View File

@@ -0,0 +1,139 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { isArray, isHex, isInstanceOf, isString } from '../util/types';
export function inAddress (address) {
// TODO: address validation if we have upper-lower addresses
return inHex(address);
}
export function inBlockNumber (blockNumber) {
if (isString(blockNumber)) {
switch (blockNumber) {
case 'earliest':
case 'latest':
case 'pending':
return blockNumber;
}
}
return inNumber16(blockNumber);
}
export function inData (data) {
if (data && data.length && !isHex(data)) {
data = data.split('').map((chr) => {
return `0${chr.charCodeAt(0).toString(16)}`.slice(-2);
}).join('');
}
return inHex(data);
}
export function inTopics (_topics) {
let topics = (_topics || [])
.filter((topic) => topic)
.map(inHex);
while (topics.length < 4) {
topics.push(null);
}
return topics;
}
export function inFilter (options) {
if (options) {
Object.keys(options).forEach((key) => {
switch (key) {
case 'address':
if (isArray(options[key])) {
options[key] = options[key].map(inAddress);
} else {
options[key] = inAddress(options[key]);
}
break;
case 'fromBlock':
case 'toBlock':
options[key] = inBlockNumber(options[key]);
break;
case 'limit':
options[key] = inNumber10(options[key]);
break;
case 'topics':
options[key] = inTopics(options[key]);
}
});
}
return options;
}
export function inHex (str) {
if (str && str.substr(0, 2) === '0x') {
return str.toLowerCase();
}
return `0x${(str || '').toLowerCase()}`;
}
export function inNumber10 (number) {
if (isInstanceOf(number, BigNumber)) {
return number.toNumber();
}
return (new BigNumber(number || 0)).toNumber();
}
export function inNumber16 (number) {
if (isInstanceOf(number, BigNumber)) {
return inHex(number.toString(16));
}
return inHex((new BigNumber(number || 0)).toString(16));
}
export function inOptions (options) {
if (options) {
Object.keys(options).forEach((key) => {
switch (key) {
case 'from':
case 'to':
options[key] = inAddress(options[key]);
break;
case 'gas':
case 'gasPrice':
case 'value':
case 'nonce':
options[key] = inNumber16(options[key]);
break;
case 'data':
options[key] = inData(options[key]);
break;
}
});
}
return options;
}

View File

@@ -0,0 +1,245 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { inAddress, inBlockNumber, inData, inFilter, inHex, inNumber10, inNumber16, inOptions } from './input';
import { isAddress } from '../../../test/types';
describe('api/format/input', () => {
const address = '0x63cf90d3f0410092fc0fca41846f596223979195';
describe('inAddress', () => {
const address = '63cf90d3f0410092fc0fca41846f596223979195';
it('adds the leading 0x as required', () => {
expect(inAddress(address)).to.equal(`0x${address}`);
});
it('returns verified addresses as-is', () => {
expect(inAddress(`0x${address}`)).to.equal(`0x${address}`);
});
it('returns lowercase equivalents', () => {
expect(inAddress(address.toUpperCase())).to.equal(`0x${address}`);
});
it('returns 0x on null addresses', () => {
expect(inAddress()).to.equal('0x');
});
});
describe('inBlockNumber()', () => {
it('returns earliest as-is', () => {
expect(inBlockNumber('earliest')).to.equal('earliest');
});
it('returns latest as-is', () => {
expect(inBlockNumber('latest')).to.equal('latest');
});
it('returns pending as-is', () => {
expect(inBlockNumber('pending')).to.equal('pending');
});
it('formats existing BigNumber into hex', () => {
expect(inBlockNumber(new BigNumber(0x123456))).to.equal('0x123456');
});
it('formats hex strings into hex', () => {
expect(inBlockNumber('0x123456')).to.equal('0x123456');
});
it('formats numbers into hex', () => {
expect(inBlockNumber(0x123456)).to.equal('0x123456');
});
});
describe('inData', () => {
it('formats to hex', () => {
expect(inData('123456')).to.equal('0x123456');
});
it('converts a string to a hex representation', () => {
expect(inData('jaco')).to.equal('0x6a61636f');
});
});
describe('inHex', () => {
it('leaves leading 0x as-is', () => {
expect(inHex('0x123456')).to.equal('0x123456');
});
it('adds a leading 0x', () => {
expect(inHex('123456')).to.equal('0x123456');
});
it('returns uppercase as lowercase (leading 0x)', () => {
expect(inHex('0xABCDEF')).to.equal('0xabcdef');
});
it('returns uppercase as lowercase (no leading 0x)', () => {
expect(inHex('ABCDEF')).to.equal('0xabcdef');
});
it('handles empty & null', () => {
expect(inHex()).to.equal('0x');
expect(inHex('')).to.equal('0x');
});
});
describe('inFilter', () => {
['address'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = inFilter(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(address);
});
});
['fromBlock', 'toBlock'].forEach((input) => {
it(`formats ${input} number as blockNumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = inFilter(block)[input];
expect(formatted).to.equal('0x123');
});
});
it('ignores and passes through unknown keys', () => {
expect(inFilter({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats an filter options object with relevant entries converted', () => {
expect(
inFilter({
address: address,
fromBlock: 'latest',
toBlock: 0x101,
extraData: 'someExtraStuffInHere',
limit: 0x32
})
).to.deep.equal({
address: address,
fromBlock: 'latest',
toBlock: '0x101',
extraData: 'someExtraStuffInHere',
limit: 50
});
});
});
describe('inNumber10()', () => {
it('formats existing BigNumber into number', () => {
expect(inNumber10(new BigNumber(123))).to.equal(123);
});
it('formats hex strings into decimal', () => {
expect(inNumber10('0x0a')).to.equal(10);
});
it('formats numbers into number', () => {
expect(inNumber10(123)).to.equal(123);
});
it('formats undefined into 0', () => {
expect(inNumber10()).to.equal(0);
});
});
describe('inNumber16()', () => {
it('formats existing BigNumber into hex', () => {
expect(inNumber16(new BigNumber(0x123456))).to.equal('0x123456');
});
it('formats hex strings into hex', () => {
expect(inNumber16('0x123456')).to.equal('0x123456');
});
it('formats numbers into hex', () => {
expect(inNumber16(0x123456)).to.equal('0x123456');
});
it('formats undefined into 0', () => {
expect(inNumber16()).to.equal('0x0');
});
});
describe('inOptions', () => {
['data'].forEach((input) => {
it(`converts ${input} to hex data`, () => {
const block = {};
block[input] = '1234';
const formatted = inData(block[input]);
expect(formatted).to.equal('0x1234');
});
});
['from', 'to'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = inOptions(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(address);
});
});
['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = inOptions(block)[input];
expect(formatted).to.equal('0x123');
});
});
it('ignores and passes through unknown keys', () => {
expect(inOptions({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats an options object with relevant entries converted', () => {
expect(
inOptions({
from: address,
to: address,
gas: new BigNumber('0x100'),
gasPrice: 0x101,
value: 258,
nonce: '0x104',
data: '0123456789',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
from: address,
to: address,
gas: '0x100',
gasPrice: '0x101',
value: '0x102',
nonce: '0x104',
data: '0x0123456789',
extraData: 'someExtraStuffInHere'
});
});
});
});

165
js/src/api/format/output.js Normal file
View File

@@ -0,0 +1,165 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { toChecksumAddress } from '../../abi/util/address';
export function outAccountInfo (infos) {
const ret = {};
Object.keys(infos).forEach((address) => {
const info = infos[address];
ret[outAddress(address)] = {
name: info.name,
uuid: info.uuid,
meta: JSON.parse(info.meta)
};
});
return ret;
}
export function outAddress (address) {
return toChecksumAddress(address);
}
export function outBlock (block) {
if (block) {
Object.keys(block).forEach((key) => {
switch (key) {
case 'author':
case 'miner':
block[key] = outAddress(block[key]);
break;
case 'difficulty':
case 'gasLimit':
case 'gasUsed':
case 'nonce':
case 'number':
case 'totalDifficulty':
block[key] = outNumber(block[key]);
break;
case 'timestamp':
block[key] = outDate(block[key]);
break;
}
});
}
return block;
}
export function outDate (date) {
return new Date(outNumber(date).toNumber() * 1000);
}
export function outLog (log) {
Object.keys(log).forEach((key) => {
switch (key) {
case 'blockNumber':
case 'logIndex':
case 'transactionIndex':
log[key] = outNumber(log[key]);
break;
case 'address':
log[key] = outAddress(log[key]);
break;
}
});
return log;
}
export function outNumber (number) {
return new BigNumber(number || 0);
}
export function outPeers (peers) {
return {
active: outNumber(peers.active),
connected: outNumber(peers.connected),
max: outNumber(peers.max)
};
}
export function outReceipt (receipt) {
if (receipt) {
Object.keys(receipt).forEach((key) => {
switch (key) {
case 'blockNumber':
case 'cumulativeGasUsed':
case 'gasUsed':
case 'transactionIndex':
receipt[key] = outNumber(receipt[key]);
break;
case 'contractAddress':
receipt[key] = outAddress(receipt[key]);
break;
}
});
}
return receipt;
}
export function outSignerRequest (request) {
if (request) {
Object.keys(request).forEach((key) => {
switch (key) {
case 'id':
request[key] = outNumber(request[key]);
break;
case 'payload':
request[key].transaction = outTransaction(request[key].transaction);
break;
}
});
}
return request;
}
export function outTransaction (tx) {
if (tx) {
Object.keys(tx).forEach((key) => {
switch (key) {
case 'blockNumber':
case 'gasPrice':
case 'gas':
case 'nonce':
case 'transactionIndex':
case 'value':
tx[key] = outNumber(tx[key]);
break;
case 'creates':
case 'from':
case 'to':
tx[key] = outAddress(tx[key]);
break;
}
});
}
return tx;
}

View File

@@ -0,0 +1,247 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { outBlock, outAccountInfo, outAddress, outDate, outNumber, outPeers, outReceipt, outTransaction } from './output';
import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types';
describe('api/format/output', () => {
const address = '0x63cf90d3f0410092fc0fca41846f596223979195';
const checksum = '0x63Cf90D3f0410092FC0fca41846f596223979195';
describe('outAccountInfo', () => {
it('returns meta objects parsed', () => {
expect(outAccountInfo(
{ '0x63cf90d3f0410092fc0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: '{"name":"456"}' }
}
)).to.deep.equal({
'0x63Cf90D3f0410092FC0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: { name: '456' }
}
});
});
});
describe('outAddress', () => {
it('retuns the address as checksummed', () => {
expect(outAddress(address)).to.equal(checksum);
});
it('retuns the checksum as checksummed', () => {
expect(outAddress(checksum)).to.equal(checksum);
});
});
describe('outBlock', () => {
['author', 'miner'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = outBlock(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(checksum);
});
});
['difficulty', 'gasLimit', 'gasUsed', 'number', 'nonce', 'totalDifficulty'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = outBlock(block)[input];
expect(isInstanceOf(formatted, BigNumber)).to.be.true;
expect(formatted.toString(16)).to.equal('123');
});
});
['timestamp'].forEach((input) => {
it(`formats ${input} number as Date`, () => {
const block = {};
block[input] = 0x57513668;
const formatted = outBlock(block)[input];
expect(isInstanceOf(formatted, Date)).to.be.true;
expect(formatted.getTime()).to.equal(1464940136000);
});
});
it('ignores and passes through unknown keys', () => {
expect(outBlock({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats a block with all the info converted', () => {
expect(
outBlock({
author: address,
miner: address,
difficulty: '0x100',
gasLimit: '0x101',
gasUsed: '0x102',
number: '0x103',
nonce: '0x104',
totalDifficulty: '0x105',
timestamp: '0x57513668',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
author: checksum,
miner: checksum,
difficulty: new BigNumber('0x100'),
gasLimit: new BigNumber('0x101'),
gasUsed: new BigNumber('0x102'),
number: new BigNumber('0x103'),
nonce: new BigNumber('0x104'),
totalDifficulty: new BigNumber('0x105'),
timestamp: new Date('2016-06-03T07:48:56.000Z'),
extraData: 'someExtraStuffInHere'
});
});
});
describe('outDate', () => {
it('converts a second date in unix timestamp', () => {
expect(outDate(0x57513668)).to.deep.equal(new Date('2016-06-03T07:48:56.000Z'));
});
});
describe('outNumber', () => {
it('returns a BigNumber equalling the value', () => {
const bn = outNumber('0x123456');
expect(isBigNumber(bn)).to.be.true;
expect(bn.eq(0x123456)).to.be.true;
});
it('assumes 0 when ivalid input', () => {
expect(outNumber().eq(0)).to.be.true;
});
});
describe('outPeers', () => {
it('converts all internal numbers to BigNumbers', () => {
expect(outPeers({ active: 789, connected: '456', max: 0x7b })).to.deep.equal({
active: new BigNumber(789),
connected: new BigNumber(456),
max: new BigNumber(123)
});
});
});
describe('outReceipt', () => {
['contractAddress'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = outReceipt(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(checksum);
});
});
['blockNumber', 'cumulativeGasUsed', 'cumulativeGasUsed', 'gasUsed', 'transactionIndex'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = outReceipt(block)[input];
expect(isInstanceOf(formatted, BigNumber)).to.be.true;
expect(formatted.toString(16)).to.equal('123');
});
});
it('ignores and passes through unknown keys', () => {
expect(outReceipt({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats a receipt with all the info converted', () => {
expect(
outReceipt({
contractAddress: address,
blockNumber: '0x100',
cumulativeGasUsed: '0x101',
gasUsed: '0x102',
transactionIndex: '0x103',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
contractAddress: checksum,
blockNumber: new BigNumber('0x100'),
cumulativeGasUsed: new BigNumber('0x101'),
gasUsed: new BigNumber('0x102'),
transactionIndex: new BigNumber('0x103'),
extraData: 'someExtraStuffInHere'
});
});
});
describe('outTransaction', () => {
['from', 'to'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = outTransaction(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(checksum);
});
});
['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = outTransaction(block)[input];
expect(isInstanceOf(formatted, BigNumber)).to.be.true;
expect(formatted.toString(16)).to.equal('123');
});
});
it('ignores and passes through unknown keys', () => {
expect(outTransaction({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats a transaction with all the info converted', () => {
expect(
outTransaction({
from: address,
to: address,
blockNumber: '0x100',
gasPrice: '0x101',
gas: '0x102',
nonce: '0x103',
transactionIndex: '0x104',
value: '0x105',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
from: checksum,
to: checksum,
blockNumber: new BigNumber('0x100'),
gasPrice: new BigNumber('0x101'),
gas: new BigNumber('0x102'),
nonce: new BigNumber('0x103'),
transactionIndex: new BigNumber('0x104'),
value: new BigNumber('0x105'),
extraData: 'someExtraStuffInHere'
});
});
});
});

17
js/src/api/index.js Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './api';

43
js/src/api/rpc/db/db.js Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inHex } from '../../format/input';
export default class Db {
constructor (transport) {
this._transport = transport;
}
getHex (dbName, keyName) {
return this._transport
.execute('db_getHex', dbName, keyName);
}
getString (dbName, keyName) {
return this._transport
.execute('db_getString', dbName, keyName);
}
putHex (dbName, keyName, hexData) {
return this._transport
.execute('db_putHex', dbName, keyName, inHex(hexData));
}
putString (dbName, keyName, stringData) {
return this._transport
.execute('db_putString', dbName, keyName, stringData);
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import Http from '../../transport/http';
import Db from './db';
const instance = new Db(new Http(TEST_HTTP_URL));
describe('api/rpc/Db', () => {
let scope;
describe('putHex', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'db_putHex', reply: { result: [] } }]);
});
it('formats the inputs correctly', () => {
return instance.putHex('db', 'key', '1234').then(() => {
expect(scope.body.db_putHex.params).to.deep.equal(['db', 'key', '0x1234']);
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './db';

View File

@@ -0,0 +1,170 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createHttpApi } from '../../../../test/e2e/ethapi';
import { isAddress } from '../../../../test/types';
describe('ethapi.eth', () => {
const ethapi = createHttpApi();
const address = '0x63cf90d3f0410092fc0fca41846f596223979195';
let latestBlockNumber;
let latestBlockHash;
describe('accounts', () => {
it('returns the available accounts', () => {
return ethapi.eth.accounts().then((accounts) => {
accounts.forEach((account) => {
expect(isAddress(account)).to.be.true;
});
});
});
});
describe('blockNumber', () => {
it('returns the current blockNumber', () => {
return ethapi.eth.blockNumber().then((blockNumber) => {
latestBlockNumber = blockNumber;
expect(blockNumber.gt(0xabcde)).to.be.true;
});
});
});
describe('coinbase', () => {
it('returns the coinbase', () => {
return ethapi.eth.coinbase().then((coinbase) => {
expect(isAddress(coinbase)).to.be.true;
});
});
});
describe('gasPrice', () => {
it('returns the current gasPrice', () => {
return ethapi.eth.gasPrice().then((gasPrice) => {
expect(gasPrice.gt(0)).to.be.true;
});
});
});
describe('getBalance', () => {
it('returns the balance for latest block', () => {
return ethapi.eth.getBalance(address).then((balance) => {
expect(balance.gt(0)).to.be.true;
});
});
it('returns the balance for a very early block', () => {
const atBlock = '0x65432';
const atValue = '18e07120a6e164fee1b';
return ethapi.eth
.getBalance(address, atBlock)
.then((balance) => {
expect(balance.toString(16)).to.equal(atValue);
})
.catch((error) => {
// Parity doesn't support pruned-before-block balance lookups
expect(error.message).to.match(/not supported/);
});
});
it('returns the balance for a recent/out-of-pruning-range block', () => {
return ethapi.eth
.getBalance(address, latestBlockNumber.minus(1000))
.then((balance) => {
expect(balance.gt(0)).to.be.true;
});
});
});
describe('getBlockByNumber', () => {
it('returns the latest block', () => {
return ethapi.eth.getBlockByNumber().then((block) => {
expect(block).to.be.ok;
});
});
it('returns a block by blockNumber', () => {
return ethapi.eth.getBlockByNumber(latestBlockNumber).then((block) => {
latestBlockHash = block.hash;
expect(block).to.be.ok;
});
});
it('returns a block by blockNumber (full)', () => {
return ethapi.eth.getBlockByNumber(latestBlockNumber, true).then((block) => {
expect(block).to.be.ok;
});
});
});
describe('getBlockByHash', () => {
it('returns the specified block', () => {
return ethapi.eth.getBlockByHash(latestBlockHash).then((block) => {
expect(block).to.be.ok;
expect(block.hash).to.equal(latestBlockHash);
});
});
it('returns the specified block (full)', () => {
return ethapi.eth.getBlockByHash(latestBlockHash, true).then((block) => {
expect(block).to.be.ok;
expect(block.hash).to.equal(latestBlockHash);
});
});
});
describe('getBlockTransactionCountByHash', () => {
it('returns the transactions of the specified hash', () => {
return ethapi.eth.getBlockTransactionCountByHash(latestBlockHash).then((count) => {
expect(count).to.be.ok;
expect(count.gte(0)).to.be.true;
});
});
});
describe('getBlockTransactionCountByNumber', () => {
it('returns the transactions of latest', () => {
return ethapi.eth.getBlockTransactionCountByNumber().then((count) => {
expect(count).to.be.ok;
expect(count.gte(0)).to.be.true;
});
});
it('returns the transactions of a specified number', () => {
return ethapi.eth.getBlockTransactionCountByNumber(latestBlockNumber).then((count) => {
expect(count).to.be.ok;
expect(count.gte(0)).to.be.true;
});
});
});
describe('getTransactionCount', () => {
it('returns the count for an address', () => {
return ethapi.eth.getTransactionCount(address).then((count) => {
expect(count).to.be.ok;
expect(count.gte(0x1000c2)).to.be.ok;
});
});
it('returns the count for an address at specified blockNumber', () => {
return ethapi.eth.getTransactionCount(address, latestBlockNumber).then((count) => {
expect(count).to.be.ok;
expect(count.gte(0x1000c2)).to.be.ok;
});
});
});
});

329
js/src/api/rpc/eth/eth.js Normal file
View File

@@ -0,0 +1,329 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inBlockNumber, inData, inFilter, inHex, inNumber16, inOptions } from '../../format/input';
import { outAddress, outBlock, outLog, outNumber, outReceipt, outTransaction } from '../../format/output';
export default class Eth {
constructor (transport) {
this._transport = transport;
}
accounts () {
return this._transport
.execute('eth_accounts')
.then((accounts) => (accounts || []).map(outAddress));
}
blockNumber () {
return this._transport
.execute('eth_blockNumber')
.then(outNumber);
}
call (options, blockNumber = 'latest') {
return this._transport
.execute('eth_call', inOptions(options), inBlockNumber(blockNumber));
}
checkRequest (requestId) {
return this._transport
.execute('eth_checkRequest', inNumber16(requestId));
}
coinbase () {
return this._transport
.execute('eth_coinbase')
.then(outAddress);
}
compileLLL (code) {
return this._transport
.execute('eth_compileLLL', inData(code));
}
compileSerpent (code) {
return this._transport
.execute('eth_compileSerpent', inData(code));
}
compileSolidity (code) {
return this._transport
.execute('eth_compileSolidity', inData(code));
}
estimateGas (options) {
return this._transport
.execute('eth_estimateGas', inOptions(options))
.then(outNumber);
}
fetchQueuedTransactions () {
return this._transport
.execute('eth_fetchQueuedTransactions');
}
flush () {
return this._transport
.execute('eth_flush');
}
gasPrice () {
return this._transport
.execute('eth_gasPrice')
.then(outNumber);
}
getBalance (address, blockNumber = 'latest') {
return this._transport
.execute('eth_getBalance', inAddress(address), inBlockNumber(blockNumber))
.then(outNumber);
}
getBlockByHash (hash, full = false) {
return this._transport
.execute('eth_getBlockByHash', inHex(hash), full)
.then(outBlock);
}
getBlockByNumber (blockNumber = 'latest', full = false) {
return this._transport
.execute('eth_getBlockByNumber', inBlockNumber(blockNumber), full)
.then(outBlock);
}
getBlockTransactionCountByHash (hash) {
return this._transport
.execute('eth_getBlockTransactionCountByHash', inHex(hash))
.then(outNumber);
}
getBlockTransactionCountByNumber (blockNumber = 'latest') {
return this._transport
.execute('eth_getBlockTransactionCountByNumber', inBlockNumber(blockNumber))
.then(outNumber);
}
getCode (address, blockNumber = 'latest') {
return this._transport
.execute('eth_getCode', inAddress(address), inBlockNumber(blockNumber));
}
getCompilers () {
return this._transport
.execute('eth_getCompilers');
}
getFilterChanges (filterId) {
return this._transport
.execute('eth_getFilterChanges', inNumber16(filterId))
.then((logs) => logs.map(outLog));
}
getFilterChangesEx (filterId) {
return this._transport
.execute('eth_getFilterChangesEx', inNumber16(filterId));
}
getFilterLogs (filterId) {
return this._transport
.execute('eth_getFilterLogs', inNumber16(filterId))
.then((logs) => logs.map(outLog));
}
getFilterLogsEx (filterId) {
return this._transport
.execute('eth_getFilterLogsEx', inNumber16(filterId));
}
getLogs (options) {
return this._transport
.execute('eth_getLogs', inFilter(options));
}
getLogsEx (options) {
return this._transport
.execute('eth_getLogsEx', inFilter(options));
}
getStorageAt (address, index = 0, blockNumber = 'latest') {
return this._transport
.execute('eth_getStorageAt', inAddress(address), inNumber16(index), inBlockNumber(blockNumber));
}
getTransactionByBlockHashAndIndex (hash, index = 0) {
return this._transport
.execute('eth_getTransactionByBlockHashAndIndex', inHex(hash), inNumber16(index))
.then(outTransaction);
}
getTransactionByBlockNumberAndIndex (blockNumber = 'latest', index = 0) {
return this._transport
.execute('eth_getTransactionByBlockNumberAndIndex', inBlockNumber(blockNumber), inNumber16(index))
.then(outTransaction);
}
getTransactionByHash (hash) {
return this._transport
.execute('eth_getTransactionByHash', inHex(hash))
.then(outTransaction);
}
getTransactionCount (address, blockNumber = 'latest') {
return this._transport
.execute('eth_getTransactionCount', inAddress(address), inBlockNumber(blockNumber))
.then(outNumber);
}
getTransactionReceipt (txhash) {
return this._transport
.execute('eth_getTransactionReceipt', inHex(txhash))
.then(outReceipt);
}
getUncleByBlockHashAndIndex (hash, index = 0) {
return this._transport
.execute('eth_getUncleByBlockHashAndIndex', inHex(hash), inNumber16(index));
}
getUncleByBlockNumberAndIndex (blockNumber = 'latest', index = 0) {
return this._transport
.execute('eth_getUncleByBlockNumberAndIndex', inBlockNumber(blockNumber), inNumber16(index));
}
getUncleCountByBlockHash (hash) {
return this._transport
.execute('eth_getUncleCountByBlockHash', inHex(hash))
.then(outNumber);
}
getUncleCountByBlockNumber (blockNumber = 'latest') {
return this._transport
.execute('eth_getUncleCountByBlockNumber', inBlockNumber(blockNumber))
.then(outNumber);
}
getWork () {
return this._transport
.execute('eth_getWork');
}
hashrate () {
return this._transport
.execute('eth_hashrate')
.then(outNumber);
}
inspectTransaction () {
return this._transport
.execute('eth_inspectTransaction');
}
mining () {
return this._transport
.execute('eth_mining');
}
newBlockFilter () {
return this._transport
.execute('eth_newBlockFilter');
}
newFilter (options) {
return this._transport
.execute('eth_newFilter', inFilter(options));
}
newFilterEx (options) {
return this._transport
.execute('eth_newFilterEx', inFilter(options));
}
newPendingTransactionFilter () {
return this._transport
.execute('eth_newPendingTransactionFilter');
}
notePassword () {
return this._transport
.execute('eth_notePassword');
}
pendingTransactions () {
return this._transport
.execute('eth_pendingTransactions');
}
postTransaction (options) {
return this._transport
.execute('eth_postTransaction', inOptions(options));
}
protocolVersion () {
return this._transport
.execute('eth_protocolVersion');
}
register () {
return this._transport
.execute('eth_register');
}
sendRawTransaction (data) {
return this._transport
.execute('eth_sendRawTransaction', inData(data));
}
sendTransaction (options) {
return this._transport
.execute('eth_sendTransaction', inOptions(options));
}
sign () {
return this._transport
.execute('eth_sign');
}
signTransaction () {
return this._transport
.execute('eth_signTransaction');
}
submitHashrate (hashrate, clientId) {
return this._transport
.execute('eth_submitHashrate', inNumber16(hashrate), clientId);
}
submitWork (nonce, powHash, mixDigest) {
return this._transport
.execute('eth_submitWork', inNumber16(nonce), powHash, mixDigest);
}
syncing () {
return this._transport
.execute('eth_syncing');
}
uninstallFilter (filterId) {
return this._transport
.execute('eth_uninstallFilter', inHex(filterId));
}
unregister () {
return this._transport
.execute('eth_unregister');
}
}

View File

@@ -0,0 +1,474 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import { isBigNumber } from '../../../../test/types';
import Http from '../../transport/http';
import Eth from './eth';
const instance = new Eth(new Http(TEST_HTTP_URL));
describe('rpc/Eth', () => {
const address = '0x63Cf90D3f0410092FC0fca41846f596223979195';
let scope;
describe('accounts', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_accounts', reply: { result: [address.toLowerCase()] } }]);
});
it('returns a list of accounts, formatted', () => {
return instance.accounts().then((accounts) => {
expect(accounts).to.deep.equal([address]);
});
});
});
describe('blockNumber', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_blockNumber', reply: { result: '0x123456' } }]);
});
it('returns the current blockNumber, formatted', () => {
return instance.blockNumber().then((blockNumber) => {
expect(isBigNumber(blockNumber)).to.be.true;
expect(blockNumber.toString(16)).to.equal('123456');
});
});
});
describe('call', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_call', reply: { result: [] } }]);
});
it('formats the input options & blockNumber', () => {
return instance.call({ data: '12345678' }, 'earliest').then(() => {
expect(scope.body.eth_call.params).to.deep.equal([{ data: '0x12345678' }, 'earliest']);
});
});
it('provides a latest blockNumber when not specified', () => {
return instance.call({ data: '12345678' }).then(() => {
expect(scope.body.eth_call.params).to.deep.equal([{ data: '0x12345678' }, 'latest']);
});
});
});
describe('coinbase', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_coinbase', reply: { result: address.toLowerCase() } }]);
});
it('returns the coinbase, formatted', () => {
return instance.coinbase().then((account) => {
expect(account).to.deep.equal(address);
});
});
});
['LLL', 'Serpent', 'Solidity'].forEach((type) => {
const method = `compile${type}`;
describe(method, () => {
beforeEach(() => {
scope = mockHttp([{ method: `eth_${method}`, reply: { result: '0x123' } }]);
});
it('formats the input as data, returns the output', () => {
return instance[method]('0xabcdef').then((result) => {
expect(scope.body[`eth_${method}`].params).to.deep.equal(['0xabcdef']);
expect(result).to.equal('0x123');
});
});
});
});
describe('estimateGas', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_estimateGas', reply: { result: '0x123' } }]);
});
it('converts the options correctly', () => {
return instance.estimateGas({ gas: 21000 }).then(() => {
expect(scope.body.eth_estimateGas.params).to.deep.equal([{ gas: '0x5208' }]);
});
});
it('returns the gas used', () => {
return instance.estimateGas({}).then((gas) => {
expect(isBigNumber(gas)).to.be.true;
expect(gas.toString(16)).to.deep.equal('123');
});
});
});
describe('gasPrice', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_gasPrice', reply: { result: '0x123' } }]);
});
it('returns the fomratted price', () => {
return instance.gasPrice().then((price) => {
expect(isBigNumber(price)).to.be.true;
expect(price.toString(16)).to.deep.equal('123');
});
});
});
describe('getBalance', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getBalance', reply: { result: '0x123' } }]);
});
it('passes in the address (default blockNumber)', () => {
return instance.getBalance(address).then(() => {
expect(scope.body.eth_getBalance.params).to.deep.equal([address.toLowerCase(), 'latest']);
});
});
it('passes in the address & blockNumber', () => {
return instance.getBalance(address, 0x456).then(() => {
expect(scope.body.eth_getBalance.params).to.deep.equal([address.toLowerCase(), '0x456']);
});
});
it('returns the balance', () => {
return instance.getBalance(address, 0x123).then((balance) => {
expect(isBigNumber(balance)).to.be.true;
expect(balance.toString(16)).to.deep.equal('123');
});
});
});
describe('getBlockByHash', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getBlockByHash', reply: { result: { miner: address.toLowerCase() } } }]);
});
it('formats the input hash as a hash, default full', () => {
return instance.getBlockByHash('1234').then(() => {
expect(scope.body.eth_getBlockByHash.params).to.deep.equal(['0x1234', false]);
});
});
it('formats the input hash as a hash, full true', () => {
return instance.getBlockByHash('1234', true).then(() => {
expect(scope.body.eth_getBlockByHash.params).to.deep.equal(['0x1234', true]);
});
});
it('formats the output into block', () => {
return instance.getBlockByHash('1234').then((block) => {
expect(block.miner).to.equal(address);
});
});
});
describe('getBlockByNumber', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getBlockByNumber', reply: { result: { miner: address.toLowerCase() } } }]);
});
it('assumes blockNumber latest & full false', () => {
return instance.getBlockByNumber().then(() => {
expect(scope.body.eth_getBlockByNumber.params).to.deep.equal(['latest', false]);
});
});
it('uses input blockNumber & full false', () => {
return instance.getBlockByNumber('0x1234').then(() => {
expect(scope.body.eth_getBlockByNumber.params).to.deep.equal(['0x1234', false]);
});
});
it('formats the input blockNumber, full true', () => {
return instance.getBlockByNumber(0x1234, true).then(() => {
expect(scope.body.eth_getBlockByNumber.params).to.deep.equal(['0x1234', true]);
});
});
it('formats the output into block', () => {
return instance.getBlockByNumber(0x1234).then((block) => {
expect(block.miner).to.equal(address);
});
});
});
describe('getBlockTransactionCountByHash', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getBlockTransactionCountByHash', reply: { result: '0x123' } }]);
});
it('formats input hash properly', () => {
return instance.getBlockTransactionCountByHash('abcdef').then(() => {
expect(scope.body.eth_getBlockTransactionCountByHash.params).to.deep.equal(['0xabcdef']);
});
});
it('formats the output number', () => {
return instance.getBlockTransactionCountByHash('0x1234').then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.toString(16)).to.equal('123');
});
});
});
describe('getBlockTransactionCountByNumber', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getBlockTransactionCountByNumber', reply: { result: '0x123' } }]);
});
it('specified blockNumber latest when none specified', () => {
return instance.getBlockTransactionCountByNumber().then(() => {
expect(scope.body.eth_getBlockTransactionCountByNumber.params).to.deep.equal(['latest']);
});
});
it('formats input blockNumber properly', () => {
return instance.getBlockTransactionCountByNumber(0xabcdef).then(() => {
expect(scope.body.eth_getBlockTransactionCountByNumber.params).to.deep.equal(['0xabcdef']);
});
});
it('formats the output number', () => {
return instance.getBlockTransactionCountByNumber('0x1234').then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.toString(16)).to.equal('123');
});
});
});
describe('getCode', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getCode', reply: { result: '0x1234567890' } }]);
});
it('passes in the address (default blockNumber)', () => {
return instance.getCode(address).then(() => {
expect(scope.body.eth_getCode.params).to.deep.equal([address.toLowerCase(), 'latest']);
});
});
it('passes in the address & blockNumber', () => {
return instance.getCode(address, 0x456).then(() => {
expect(scope.body.eth_getCode.params).to.deep.equal([address.toLowerCase(), '0x456']);
});
});
it('returns the code', () => {
return instance.getCode(address, 0x123).then((code) => {
expect(code).to.equal('0x1234567890');
});
});
});
describe('getStorageAt', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getStorageAt', reply: { result: '0x1234567890' } }]);
});
it('passes in the address (default index& blockNumber)', () => {
return instance.getStorageAt(address).then(() => {
expect(scope.body.eth_getStorageAt.params).to.deep.equal([address.toLowerCase(), '0x0', 'latest']);
});
});
it('passes in the address, index & blockNumber', () => {
return instance.getStorageAt(address, 15, 0x456).then(() => {
expect(scope.body.eth_getStorageAt.params).to.deep.equal([address.toLowerCase(), '0xf', '0x456']);
});
});
it('returns the storage', () => {
return instance.getStorageAt(address, 0x123).then((storage) => {
expect(storage).to.equal('0x1234567890');
});
});
});
describe('getTransactionByBlockHashAndIndex', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getTransactionByBlockHashAndIndex', reply: { result: { to: address.toLowerCase() } } }]);
});
it('passes in the hash (default index)', () => {
return instance.getTransactionByBlockHashAndIndex('12345').then(() => {
expect(scope.body.eth_getTransactionByBlockHashAndIndex.params).to.deep.equal(['0x12345', '0x0']);
});
});
it('passes in the hash & specified index', () => {
return instance.getTransactionByBlockHashAndIndex('6789', 0x456).then(() => {
expect(scope.body.eth_getTransactionByBlockHashAndIndex.params).to.deep.equal(['0x6789', '0x456']);
});
});
it('returns the formatted transaction', () => {
return instance.getTransactionByBlockHashAndIndex('6789', 0x123).then((tx) => {
expect(tx).to.deep.equal({ to: address });
});
});
});
describe('getTransactionByBlockNumberAndIndex', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getTransactionByBlockNumberAndIndex', reply: { result: { to: address.toLowerCase() } } }]);
});
it('passes in the default parameters', () => {
return instance.getTransactionByBlockNumberAndIndex().then(() => {
expect(scope.body.eth_getTransactionByBlockNumberAndIndex.params).to.deep.equal(['latest', '0x0']);
});
});
it('passes in the blockNumber & specified index', () => {
return instance.getTransactionByBlockNumberAndIndex('0x6789', 0x456).then(() => {
expect(scope.body.eth_getTransactionByBlockNumberAndIndex.params).to.deep.equal(['0x6789', '0x456']);
});
});
it('returns the formatted transaction', () => {
return instance.getTransactionByBlockNumberAndIndex('0x6789', 0x123).then((tx) => {
expect(tx).to.deep.equal({ to: address });
});
});
});
describe('getTransactionByHash', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getTransactionByHash', reply: { result: { to: address.toLowerCase() } } }]);
});
it('passes in the hash', () => {
return instance.getTransactionByHash('12345').then(() => {
expect(scope.body.eth_getTransactionByHash.params).to.deep.equal(['0x12345']);
});
});
it('returns the formatted transaction', () => {
return instance.getTransactionByHash('6789').then((tx) => {
expect(tx).to.deep.equal({ to: address });
});
});
});
describe('getTransactionCount', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getTransactionCount', reply: { result: '0x123' } }]);
});
it('passes in the address (default blockNumber)', () => {
return instance.getTransactionCount(address).then(() => {
expect(scope.body.eth_getTransactionCount.params).to.deep.equal([address.toLowerCase(), 'latest']);
});
});
it('passes in the address & blockNumber', () => {
return instance.getTransactionCount(address, 0x456).then(() => {
expect(scope.body.eth_getTransactionCount.params).to.deep.equal([address.toLowerCase(), '0x456']);
});
});
it('returns the count, formatted', () => {
return instance.getTransactionCount(address, 0x123).then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.toString(16)).to.equal('123');
});
});
});
describe('getUncleByBlockHashAndIndex', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getUncleByBlockHashAndIndex', reply: { result: [] } }]);
});
it('passes in the hash (default index)', () => {
return instance.getUncleByBlockHashAndIndex('12345').then(() => {
expect(scope.body.eth_getUncleByBlockHashAndIndex.params).to.deep.equal(['0x12345', '0x0']);
});
});
it('passes in the hash & specified index', () => {
return instance.getUncleByBlockHashAndIndex('6789', 0x456).then(() => {
expect(scope.body.eth_getUncleByBlockHashAndIndex.params).to.deep.equal(['0x6789', '0x456']);
});
});
});
describe('getUncleByBlockNumberAndIndex', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getUncleByBlockNumberAndIndex', reply: { result: [] } }]);
});
it('passes in the default parameters', () => {
return instance.getUncleByBlockNumberAndIndex().then(() => {
expect(scope.body.eth_getUncleByBlockNumberAndIndex.params).to.deep.equal(['latest', '0x0']);
});
});
it('passes in the blockNumber & specified index', () => {
return instance.getUncleByBlockNumberAndIndex('0x6789', 0x456).then(() => {
expect(scope.body.eth_getUncleByBlockNumberAndIndex.params).to.deep.equal(['0x6789', '0x456']);
});
});
});
describe('getUncleCountByBlockHash', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getUncleCountByBlockHash', reply: { result: '0x123' } }]);
});
it('passes in the hash', () => {
return instance.getUncleCountByBlockHash('12345').then(() => {
expect(scope.body.eth_getUncleCountByBlockHash.params).to.deep.equal(['0x12345']);
});
});
it('formats the output number', () => {
return instance.getUncleCountByBlockHash('0x1234').then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.toString(16)).to.equal('123');
});
});
});
describe('getUncleCountByBlockNumber', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_getUncleCountByBlockNumber', reply: { result: '0x123' } }]);
});
it('passes in the default parameters', () => {
return instance.getUncleCountByBlockNumber().then(() => {
expect(scope.body.eth_getUncleCountByBlockNumber.params).to.deep.equal(['latest']);
});
});
it('passes in the blockNumber', () => {
return instance.getUncleCountByBlockNumber('0x6789').then(() => {
expect(scope.body.eth_getUncleCountByBlockNumber.params).to.deep.equal(['0x6789']);
});
});
it('formats the output number', () => {
return instance.getUncleCountByBlockNumber('0x1234').then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.toString(16)).to.equal('123');
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './eth';

View File

@@ -0,0 +1,61 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createHttpApi } from '../../../../test/e2e/ethapi';
describe('ethapi.ethcore', () => {
const ethapi = createHttpApi();
describe('gasFloorTarget', () => {
it('returns and translates the target', () => {
return ethapi.ethcore.gasFloorTarget().then((value) => {
expect(value.gt(0)).to.be.true;
});
});
});
describe('netChain', () => {
it('returns and the chain', () => {
return ethapi.ethcore.netChain().then((value) => {
expect(value).to.equal('morden');
});
});
});
describe('netPort', () => {
it('returns and translates the port', () => {
return ethapi.ethcore.netPort().then((value) => {
expect(value.gt(0)).to.be.true;
});
});
});
describe('transactionsLimit', () => {
it('returns and translates the limit', () => {
return ethapi.ethcore.transactionsLimit().then((value) => {
expect(value.gt(0)).to.be.true;
});
});
});
describe('rpcSettings', () => {
it('returns and translates the settings', () => {
return ethapi.ethcore.rpcSettings().then((value) => {
expect(value).to.be.ok;
});
});
});
});

View File

@@ -0,0 +1,168 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inData, inNumber16 } from '../../format/input';
import { outAddress, outNumber, outPeers } from '../../format/output';
export default class Ethcore {
constructor (transport) {
this._transport = transport;
}
acceptNonReservedPeers () {
return this._transport
.execute('ethcore_acceptNonReservedPeers');
}
addReservedPeer (encode) {
return this._transport
.execute('ethcore_addReservedPeer', encode);
}
defaultExtraData () {
return this._transport
.execute('ethcore_defaultExtraData');
}
devLogs () {
return this._transport
.execute('ethcore_devLogs');
}
devLogsLevels () {
return this._transport
.execute('ethcore_devLogsLevels');
}
dropNonReservedPeers () {
return this._transport
.execute('ethcore_dropNonReservedPeers');
}
extraData () {
return this._transport
.execute('ethcore_extraData');
}
gasFloorTarget () {
return this._transport
.execute('ethcore_gasFloorTarget')
.then(outNumber);
}
generateSecretPhrase () {
return this._transport
.execute('ethcore_generateSecretPhrase');
}
hashContent (url) {
return this._transport
.execute('ethcore_hashContent', url);
}
minGasPrice () {
return this._transport
.execute('ethcore_minGasPrice')
.then(outNumber);
}
netChain () {
return this._transport
.execute('ethcore_netChain');
}
netPeers () {
return this._transport
.execute('ethcore_netPeers')
.then(outPeers);
}
netMaxPeers () {
return this._transport
.execute('ethcore_netMaxPeers')
.then(outNumber);
}
netPort () {
return this._transport
.execute('ethcore_netPort')
.then(outNumber);
}
nodeName () {
return this._transport
.execute('ethcore_nodeName');
}
phraseToAddress (phrase) {
return this._transport
.execute('ethcore_phraseToAddress', phrase)
.then(outAddress);
}
registryAddress () {
return this._transport
.execute('ethcore_registryAddress')
.then(outAddress);
}
removeReservedPeer (encode) {
return this._transport
.execute('ethcore_removeReservedPeer', encode);
}
rpcSettings () {
return this._transport
.execute('ethcore_rpcSettings');
}
setAuthor (address) {
return this._transport
.execute('ethcore_setAuthor', inAddress(address));
}
setExtraData (data) {
return this._transport
.execute('ethcore_setExtraData', inData(data));
}
setGasFloorTarget (quantity) {
return this._transport
.execute('ethcore_setGasFloorTarget', inNumber16(quantity));
}
setMinGasPrice (quantity) {
return this._transport
.execute('ethcore_setMinGasPrice', inNumber16(quantity));
}
setTransactionsLimit (quantity) {
return this._transport
.execute('ethcore_setTransactionsLimit', inNumber16(quantity));
}
transactionsLimit () {
return this._transport
.execute('ethcore_transactionsLimit')
.then(outNumber);
}
unsignedTransactionsCount () {
return this._transport
.execute('ethcore_unsignedTransactionsCount')
.then(outNumber);
}
}

View File

@@ -0,0 +1,92 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import { isBigNumber } from '../../../../test/types';
import Http from '../../transport/http';
import Ethcore from './ethcore';
const instance = new Ethcore(new Http(TEST_HTTP_URL));
describe('api/rpc/Ethcore', () => {
describe('gasFloorTarget', () => {
it('returns the gasfloor, formatted', () => {
mockHttp([{ method: 'ethcore_gasFloorTarget', reply: { result: '0x123456' } }]);
return instance.gasFloorTarget().then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.eq(0x123456)).to.be.true;
});
});
});
describe('minGasPrice', () => {
it('returns the min gasprice, formatted', () => {
mockHttp([{ method: 'ethcore_minGasPrice', reply: { result: '0x123456' } }]);
return instance.minGasPrice().then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.eq(0x123456)).to.be.true;
});
});
});
describe('netMaxPeers', () => {
it('returns the max peers, formatted', () => {
mockHttp([{ method: 'ethcore_netMaxPeers', reply: { result: 25 } }]);
return instance.netMaxPeers().then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.eq(25)).to.be.true;
});
});
});
describe('newPeers', () => {
it('returns the peer structure, formatted', () => {
mockHttp([{ method: 'ethcore_netPeers', reply: { result: { active: 123, connected: 456, max: 789 } } }]);
return instance.netPeers().then((peers) => {
expect(peers.active.eq(123)).to.be.true;
expect(peers.connected.eq(456)).to.be.true;
expect(peers.max.eq(789)).to.be.true;
});
});
});
describe('netPort', () => {
it('returns the connected port, formatted', () => {
mockHttp([{ method: 'ethcore_netPort', reply: { result: 33030 } }]);
return instance.netPort().then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.eq(33030)).to.be.true;
});
});
});
describe('transactionsLimit', () => {
it('returns the tx limit, formatted', () => {
mockHttp([{ method: 'ethcore_transactionsLimit', reply: { result: 1024 } }]);
return instance.transactionsLimit().then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.eq(1024)).to.be.true;
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './ethcore';

24
js/src/api/rpc/index.js Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export Db from './db';
export Eth from './eth';
export Ethcore from './ethcore';
export Net from './net';
export Personal from './personal';
export Shh from './shh';
export Trace from './trace';
export Web3 from './web3';

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './net';

View File

@@ -0,0 +1,46 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createHttpApi } from '../../../../test/e2e/ethapi';
import { isBoolean } from '../../../../test/types';
describe('ethapi.net', () => {
const ethapi = createHttpApi();
describe('listening', () => {
it('returns the listening status', () => {
return ethapi.net.listening().then((status) => {
expect(isBoolean(status)).to.be.true;
});
});
});
describe('peerCount', () => {
it('returns the peer count', () => {
return ethapi.net.peerCount().then((count) => {
expect(count.gte(0)).to.be.true;
});
});
});
describe('version', () => {
it('returns the version', () => {
return ethapi.net.version().then((version) => {
expect(version).to.be.ok;
});
});
});
});

39
js/src/api/rpc/net/net.js Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { outNumber } from '../../format/output';
export default class Net {
constructor (transport) {
this._transport = transport;
}
listening () {
return this._transport
.execute('net_listening');
}
peerCount () {
return this._transport
.execute('net_peerCount')
.then(outNumber);
}
version () {
return this._transport
.execute('net_version');
}
}

View File

@@ -0,0 +1,36 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import { isBigNumber } from '../../../../test/types';
import Http from '../../transport/http';
import Net from './net';
const instance = new Net(new Http(TEST_HTTP_URL));
describe('api/rpc/Net', () => {
describe('peerCount', () => {
it('returns the connected peers, formatted', () => {
mockHttp([{ method: 'net_peerCount', reply: { result: '0x123456' } }]);
return instance.peerCount().then((count) => {
expect(isBigNumber(count)).to.be.true;
expect(count.eq(0x123456)).to.be.true;
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './personal';

View File

@@ -0,0 +1,53 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { createHttpApi } from '../../../../test/e2e/ethapi';
import { isAddress, isBoolean } from '../../../../test/types';
describe.skip('ethapi.personal', () => {
const ethapi = createHttpApi();
const password = 'P@55word';
let address;
describe('newAccount', () => {
it('creates a new account', () => {
return ethapi.personal.newAccount(password).then((_address) => {
address = _address;
expect(isAddress(address)).to.be.ok;
});
});
});
describe('listAccounts', () => {
it('has the newly-created account', () => {
return ethapi.personal.listAccounts(password).then((accounts) => {
expect(accounts.filter((_address) => _address === address)).to.deep.equal([address]);
accounts.forEach((account) => {
expect(isAddress(account)).to.be.true;
});
});
});
});
describe('unlockAccount', () => {
it('unlocks the newly-created account', () => {
return ethapi.personal.unlockAccount(address, password).then((result) => {
expect(isBoolean(result)).to.be.true;
expect(result).to.be.true;
});
});
});
});

View File

@@ -0,0 +1,112 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inNumber10, inNumber16, inOptions } from '../../format/input';
import { outAccountInfo, outAddress, outSignerRequest } from '../../format/output';
export default class Personal {
constructor (transport) {
this._transport = transport;
}
accountsInfo () {
return this._transport
.execute('personal_accountsInfo')
.then(outAccountInfo);
}
confirmRequest (requestId, options, password) {
return this._transport
.execute('personal_confirmRequest', inNumber16(requestId), options, password);
}
generateAuthorizationToken () {
return this._transport
.execute('personal_generateAuthorizationToken');
}
listAccounts () {
return this._transport
.execute('personal_listAccounts')
.then((accounts) => (accounts || []).map(outAddress));
}
listGethAccounts () {
return this._transport
.execute('personal_listGethAccounts')
.then((accounts) => (accounts || []).map(outAddress));
}
importGethAccounts (accounts) {
return this._transport
.execute('personal_importGethAccounts', (accounts || []).map(inAddress))
.then((accounts) => (accounts || []).map(outAddress));
}
newAccount (password) {
return this._transport
.execute('personal_newAccount', password)
.then(outAddress);
}
newAccountFromPhrase (phrase, password) {
return this._transport
.execute('personal_newAccountFromPhrase', phrase, password)
.then(outAddress);
}
newAccountFromWallet (json, password) {
return this._transport
.execute('personal_newAccountFromWallet', json, password)
.then(outAddress);
}
rejectRequest (requestId) {
return this._transport
.execute('personal_rejectRequest', inNumber16(requestId));
}
requestsToConfirm () {
return this._transport
.execute('personal_requestsToConfirm')
.then((requests) => (requests || []).map(outSignerRequest));
}
setAccountName (address, name) {
return this._transport
.execute('personal_setAccountName', inAddress(address), name);
}
setAccountMeta (address, meta) {
return this._transport
.execute('personal_setAccountMeta', inAddress(address), JSON.stringify(meta));
}
signAndSendTransaction (options, password) {
return this._transport
.execute('personal_signAndSendTransaction', inOptions(options), password);
}
signerEnabled () {
return this._transport
.execute('personal_signerEnabled');
}
unlockAccount (account, password, duration = 1) {
return this._transport
.execute('personal_unlockAccount', inAddress(account), password, inNumber10(duration));
}
}

View File

@@ -0,0 +1,97 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import Http from '../../transport/http';
import Personal from './personal';
const instance = new Personal(new Http(TEST_HTTP_URL));
describe('rpc/Personal', () => {
const account = '0x63cf90d3f0410092fc0fca41846f596223979195';
const checksum = '0x63Cf90D3f0410092FC0fca41846f596223979195';
let scope;
describe('accountsInfo', () => {
it('retrieves the available account info', () => {
scope = mockHttp([{ method: 'personal_accountsInfo', reply: {
result: {
'0x63cf90d3f0410092fc0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: '{"data":"data"}'
}
}
} }]);
return instance.accountsInfo().then((result) => {
expect(result).to.deep.equal({
'0x63Cf90D3f0410092FC0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: {
data: 'data'
}
}
});
});
});
});
describe('listAccounts', () => {
it('retrieves a list of available accounts', () => {
scope = mockHttp([{ method: 'personal_listAccounts', reply: { result: [account] } }]);
return instance.listAccounts().then((result) => {
expect(result).to.deep.equal([checksum]);
});
});
it('returns an empty list when none available', () => {
scope = mockHttp([{ method: 'personal_listAccounts', reply: { result: null } }]);
return instance.listAccounts().then((result) => {
expect(result).to.deep.equal([]);
});
});
});
describe('newAccount', () => {
it('passes the password, returning the address', () => {
scope = mockHttp([{ method: 'personal_newAccount', reply: { result: account } }]);
return instance.newAccount('password').then((result) => {
expect(scope.body.personal_newAccount.params).to.deep.equal(['password']);
expect(result).to.equal(checksum);
});
});
});
describe('unlockAccount', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'personal_unlockAccount', reply: { result: [] } }]);
});
it('passes account, password & duration', () => {
return instance.unlockAccount(account, 'password', 0xf).then(() => {
expect(scope.body.personal_unlockAccount.params).to.deep.equal([account, 'password', 15]);
});
});
it('provides a default duration when not specified', () => {
return instance.unlockAccount(account, 'password').then(() => {
expect(scope.body.personal_unlockAccount.params).to.deep.equal([account, 'password', 1]);
});
});
});
});

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './shh';

71
js/src/api/rpc/shh/shh.js Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default class Personal {
constructor (transport) {
this._transport = transport;
}
addToGroup (identity) {
return this._transport
.execute('shh_addToGroup', identity);
}
getFilterChanges (filterId) {
return this._transport
.execute('shh_getFilterChanges', filterId);
}
getMessages (filterId) {
return this._transport
.execute('shh_getMessages', filterId);
}
hasIdentity (identity) {
return this._transport
.execute('shh_hasIdentity', identity);
}
newFilter (options) {
return this._transport
.execute('shh_newFilter', options);
}
newGroup () {
return this._transport
.execute('shh_newGroup');
}
newIdentity () {
return this._transport
.execute('shh_newIdentity');
}
post (options) {
return this._transport
.execute('shh_post', options);
}
uninstallFilter (filterId) {
return this._transport
.execute('shh_uninstallFilter', filterId);
}
version () {
return this._transport
.execute('shh_version');
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './trace';

Some files were not shown because too many files have changed in this diff Show More