Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A thin, fast, low-level Promise-based wrapper around the Ethereum APIs.
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 --jsonrpc`)
- `npm run testE2E` (E2E against a running RPC-enabled testnet Parity/Geth instance, `parity --testnet --rpc`)

## installation

Expand Down
50 changes: 48 additions & 2 deletions lib/rpc/eth.e2e.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,65 @@
import BigNumber from 'bignumber.js';

import ethapi from '../../test/ethapi';
import { isAddress } from '../../test/types';

describe('ethapi.eth', () => {
describe('.accounts()', () => {
it('returns the available accounts', () => {
return ethapi.eth.accounts().then((accounts) => {
expect(accounts).to.be.ok;
accounts.forEach((account) => {
expect(isAddress(account)).to.be.true;
});
});
});
});

describe('.blockNumber()', () => {
it('returns the current blockNumber', () => {
return ethapi.eth.blockNumber().then((blockNumber) => {
expect(blockNumber).to.be.ok;
expect(blockNumber.gt(new BigNumber('0xabcde', 16))).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()', () => {
const address = '0x63cf90d3f0410092fc0fca41846f596223979195';

it('returns the balance for latest block', () => {
return ethapi.eth.getBalance(address).then((balance) => {
expect(balance.gte(0)).to.be.true;
});
});

it('returns the balance for a specified 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 at-block balance lookups just yet
expect(error.message).to.match(/32000 Unsupported request/);
});
});
});
});
18 changes: 10 additions & 8 deletions lib/rpc/eth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fromNumber } from '../util/format';
import { toBlockNumber, toHex, toNumber } from '../util/format'; // eslint-disable-line no-duplicate-imports
import { fromAddress, fromNumber } from '../util/format';
import { toAddress, toBlockNumber, toHex, toNumber } from '../util/format'; // eslint-disable-line no-duplicate-imports

export default class Eth {
constructor (transport) {
Expand All @@ -8,7 +8,8 @@ export default class Eth {

accounts () {
return this._transport
.execute('eth_accounts');
.execute('eth_accounts')
.then((accounts) => (accounts || []).map(fromAddress));
}

blockNumber () {
Expand All @@ -24,7 +25,8 @@ export default class Eth {

coinbase () {
return this._transport
.execute('eth_coinbase');
.execute('eth_coinbase')
.then(fromAddress);
}

compileLLL (code) {
Expand Down Expand Up @@ -66,7 +68,7 @@ export default class Eth {

getBalance (address, blockNumber = 'latest') {
return this._transport
.execute('eth_getBalance', toHex(address), toBlockNumber(blockNumber))
.execute('eth_getBalance', toAddress(address), toBlockNumber(blockNumber))
.then(fromNumber);
}

Expand All @@ -92,7 +94,7 @@ export default class Eth {

getCode (address, blockNumber = 'latest') {
return this._transport
.execute('eth_getCode', toHex(address), toBlockNumber(blockNumber));
.execute('eth_getCode', toAddress(address), toBlockNumber(blockNumber));
}

getCompilers () {
Expand Down Expand Up @@ -132,7 +134,7 @@ export default class Eth {

getStorageAt (address, index = 0, blockNumber = 'latest') {
return this._transport
.execute('eth_getStorageAt', toHex(address), toNumber(index), toBlockNumber(blockNumber));
.execute('eth_getStorageAt', toAddress(address), toNumber(index), toBlockNumber(blockNumber));
}

getTransactionByBlockHashAndIndex (hash, index = 0) {
Expand All @@ -152,7 +154,7 @@ export default class Eth {

getTransactionCount (address, blockNumber = 'latest') {
return this._transport
.execute('eth_getTransactionCount', toHex(address), toBlockNumber(blockNumber));
.execute('eth_getTransactionCount', toAddress(address), toBlockNumber(blockNumber));
}

getTransactionReceipt (txhash) {
Expand Down
12 changes: 8 additions & 4 deletions lib/rpc/personal.e2e.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ethapi from '../../test/ethapi';

import { isAddress, isBoolean } from '../../test/types';

describe('ethapi.personal', () => {
const password = 'P@55word';
let address;
Expand All @@ -8,25 +10,27 @@ describe('ethapi.personal', () => {
it('creates a new account', () => {
return ethapi.personal.newAccount(password).then((_address) => {
address = _address;
expect(address).to.be.ok;
expect(isAddress(address)).to.be.ok;
});
});
});

describe('.listAccounts', () => {
it('has the newly-created account', () => {
return ethapi.personal.listAccounts(password).then((accounts) => {
expect(accounts).to.be.ok;
expect(accounts.length).to.be.gt(0);
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(result).to.be.ok;
expect(isBoolean(result)).to.be.true;
expect(result).to.be.true;
});
});
});
Expand Down
17 changes: 13 additions & 4 deletions lib/rpc/personal.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import { fromAddress } from '../util/format';
import { toAddress, toNumber } from '../util/format'; // eslint-disable-line no-duplicate-imports

export default class Personal {
constructor (transport) {
this._transport = transport;
}

listAccounts () {
return this._transport.execute('personal_listAccounts');
return this._transport
.execute('personal_listAccounts')
.then((accounts) => (accounts || []).map(fromAddress));
}

newAccount (password) {
return this._transport.execute('personal_newAccount', password);
return this._transport
.execute('personal_newAccount', password)
.then(fromAddress);
}

signAndSendTransaction (txObject, password) {
return this._transport.execute('personal_signAndSendTransaction', txObject, password);
return this._transport
.execute('personal_signAndSendTransaction', txObject, password);
}

unlockAccount (account, password, duration = 5) {
return this._transport.execute('personal_unlockAccount', account, password, duration);
return this._transport
.execute('personal_unlockAccount', toAddress(account), password, toNumber(duration));
}
}
10 changes: 6 additions & 4 deletions lib/rpc/personal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const instance = new Personal(new JsonRpc(TEST_HOST, TEST_PORT));
describe('lib/Personal', () => {
endpointDescribe(instance, 'personal');

const account = '0x63cf90d3f0410092fc0fca41846f596223979195';

describe('unlockAccount', () => {
let scope;

Expand All @@ -17,17 +19,17 @@ describe('lib/Personal', () => {

it('passes account, password & duration', () => {
return instance
.unlockAccount('account', 'password', 'duration')
.unlockAccount(account, 'password', 0x123)
.then(() => {
expect(scope.body.personal_unlockAccount.params).to.deep.equal(['account', 'password', 'duration']);
expect(scope.body.personal_unlockAccount.params).to.deep.equal([account, 'password', '0x123']);
});
});

it('provides a default duration when not specified', () => {
return instance
.unlockAccount('account', 'password')
.unlockAccount(account, 'password')
.then(() => {
expect(scope.body.personal_unlockAccount.params).to.deep.equal(['account', 'password', 5]);
expect(scope.body.personal_unlockAccount.params).to.deep.equal([account, 'password', '0x5']);
});
});
});
Expand Down
8 changes: 6 additions & 2 deletions lib/rpc/web3.e2e.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import ethapi from '../../test/ethapi';

import { isHexNumber } from '../../test/types';

describe('ethapi.web3', () => {
describe('.clientVersion()', () => {
it('returns the client version', () => {
return ethapi.web3.clientVersion().then((version) => {
expect(version).to.be.ok;
const [client] = version.split('/');

expect(client === 'Parity' || client === 'Geth').to.be.ok;
});
});
});
Expand All @@ -15,7 +19,7 @@ describe('ethapi.web3', () => {
const hexStr = 'baz()'.split('').map((char) => char.charCodeAt(0).toString(16)).join('');

return ethapi.web3.sha3(`0x${hexStr}`).then((hash) => {
expect(hash).to.be.ok;
expect(isHexNumber(hash)).to.be.true;
expect(hash).to.equal(sha);
});
});
Expand Down
4 changes: 2 additions & 2 deletions lib/transport/jsonRpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default class JsonRpc {
.then((response) => {
if (response.status !== 200) {
if (this._debug) {
console.error(' ', `${method}(${params}) throws`, response.status, response.statusText);
console.error(' ', `${method}(${params}) =`, response.status, response.statusText);
}

throw new Error(`${response.status}: ${response.statusText}`);
Expand All @@ -50,7 +50,7 @@ export default class JsonRpc {
.then((result) => {
if (result.error) {
if (this._debug) {
console.error(' ', `${method}(${params}) =`, result);
console.error(' ', `${method}(${params}) =`, result.error.code, result.error.message);
}

throw new Error(`${result.error.code}: ${result.error.message}`);
Expand Down
10 changes: 10 additions & 0 deletions lib/util/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ import BigNumber from 'bignumber.js';

import { isInstanceOf, isString } from './types';

export function fromAddress (address) {
// TODO: address conversion to upper-lower
return address;
}

export function fromNumber (number) {
return new BigNumber(number || 0);
}

export function toAddress (address) {
// TODO: address validation if we have upper-lower addresses
return toHex((address || '').toLowerCase());
}

export function toBlockNumber (blockNumber) {
if (isString(blockNumber)) {
switch (blockNumber) {
Expand Down
40 changes: 34 additions & 6 deletions lib/util/format.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import BigNumber from 'bignumber.js';

import { fromNumber } from './format';
import { toBlockNumber, toHex, toNumber } from './format'; // eslint-disable-line no-duplicate-imports
import { fromAddress, fromNumber } from './format';
import { toAddress, toBlockNumber, toHex, toNumber } from './format'; // eslint-disable-line no-duplicate-imports
import { isInstanceOf } from './types';

describe('util/format', () => {
describe('.fromNumber', () => {
describe('fromAddress', () => {
const address = '0x63cf90d3f0410092fc0fca41846f596223979195';

it('retuns the address as-is', () => {
expect(fromAddress(address)).to.equal(address);
});
});

describe('fromNumber', () => {
it('returns a BigNumber equalling the value', () => {
const bn = fromNumber('0x123456');

Expand All @@ -18,7 +26,27 @@ describe('util/format', () => {
});
});

describe('.toBlockNumber()', () => {
describe('toAddress', () => {
const address = '63cf90d3f0410092fc0fca41846f596223979195';

it('adds the leading 0x as required', () => {
expect(toAddress(address)).to.equal(`0x${address}`);
});

it('returns verified addresses as-is', () => {
expect(toAddress(`0x${address}`)).to.equal(`0x${address}`);
});

it('returns lowercase equivalents', () => {
expect(toAddress(address.toUpperCase())).to.equal(`0x${address}`);
});

it('returns 0x on null addresses', () => {
expect(toAddress()).to.equal('0x');
});
});

describe('toBlockNumber()', () => {
it('returns earliest as-is', () => {
expect(toBlockNumber('earliest')).to.equal('earliest');
});
Expand All @@ -44,7 +72,7 @@ describe('util/format', () => {
});
});

describe('.toHex', () => {
describe('toHex', () => {
it('leaves leading 0x as-is', () => {
expect(toHex('0x123456')).to.equal('0x123456');
});
Expand All @@ -59,7 +87,7 @@ describe('util/format', () => {
});
});

describe('.toNumber()', () => {
describe('toNumber()', () => {
it('formats existing BigNumber into hex', () => {
expect(toNumber(new BigNumber(0x123456))).to.equal('0x123456');
});
Expand Down
Loading