Ui 2 shared package (redux, mobx, utils) (#5579)
* Create @parity/shared * Move ~/util to shared/util * Move ~/contracts to shared/contracts * Move ~/config to shared/config * Move ~/environment to shared/environment * Updated paths * Move ~/mobx to shared/mobx * Move ~/redux to shared/redux * Add shared to test babel compile
This commit is contained in:
@@ -1,149 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { range } from 'lodash';
|
||||
|
||||
const ARRAY_TYPE = 'ARRAY_TYPE';
|
||||
const ADDRESS_TYPE = 'ADDRESS_TYPE';
|
||||
const STRING_TYPE = 'STRING_TYPE';
|
||||
const BOOL_TYPE = 'BOOL_TYPE';
|
||||
const BYTES_TYPE = 'BYTES_TYPE';
|
||||
const INT_TYPE = 'INT_TYPE';
|
||||
const FIXED_TYPE = 'FIXED_TYPE';
|
||||
|
||||
export const ABI_TYPES = {
|
||||
ARRAY: ARRAY_TYPE, ADDRESS: ADDRESS_TYPE,
|
||||
STRING: STRING_TYPE, BOOL: BOOL_TYPE,
|
||||
BYTES: BYTES_TYPE, INT: INT_TYPE,
|
||||
FIXED: FIXED_TYPE
|
||||
};
|
||||
|
||||
export function parseAbiType (type) {
|
||||
const arrayRegex = /^(.+)\[(\d*)]$/;
|
||||
|
||||
if (arrayRegex.test(type)) {
|
||||
const matches = arrayRegex.exec(type);
|
||||
|
||||
const subtype = parseAbiType(matches[1]);
|
||||
const M = parseInt(matches[2]) || null;
|
||||
const defaultValue = !M
|
||||
? []
|
||||
: range(M).map(() => subtype.default);
|
||||
|
||||
return {
|
||||
type: ARRAY_TYPE,
|
||||
subtype: subtype,
|
||||
length: M,
|
||||
default: defaultValue
|
||||
};
|
||||
}
|
||||
|
||||
const lengthRegex = /^(u?int|bytes)(\d{1,3})$/;
|
||||
|
||||
if (lengthRegex.test(type)) {
|
||||
const matches = lengthRegex.exec(type);
|
||||
|
||||
const subtype = parseAbiType(matches[1]);
|
||||
const length = parseInt(matches[2]);
|
||||
|
||||
return {
|
||||
...subtype,
|
||||
length
|
||||
};
|
||||
}
|
||||
|
||||
const fixedLengthRegex = /^(u?fixed)(\d{1,3})x(\d{1,3})$/;
|
||||
|
||||
if (fixedLengthRegex.test(type)) {
|
||||
const matches = fixedLengthRegex.exec(type);
|
||||
|
||||
const subtype = parseAbiType(matches[1]);
|
||||
const M = parseInt(matches[2]);
|
||||
const N = parseInt(matches[3]);
|
||||
|
||||
return {
|
||||
...subtype,
|
||||
M, N
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'string') {
|
||||
return {
|
||||
type: STRING_TYPE,
|
||||
default: ''
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'bool') {
|
||||
return {
|
||||
type: BOOL_TYPE,
|
||||
default: false
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'address') {
|
||||
return {
|
||||
type: ADDRESS_TYPE,
|
||||
default: ''
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'bytes' || type === 'fixedBytes') {
|
||||
return {
|
||||
type: BYTES_TYPE,
|
||||
default: '0x'
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'uint') {
|
||||
return {
|
||||
type: INT_TYPE,
|
||||
default: 0,
|
||||
length: 256,
|
||||
signed: false
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'int') {
|
||||
return {
|
||||
type: INT_TYPE,
|
||||
default: 0,
|
||||
length: 256,
|
||||
signed: true
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'ufixed') {
|
||||
return {
|
||||
type: FIXED_TYPE,
|
||||
default: 0,
|
||||
length: 256,
|
||||
signed: false
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'fixed') {
|
||||
return {
|
||||
type: FIXED_TYPE,
|
||||
default: 0,
|
||||
length: 256,
|
||||
signed: true
|
||||
};
|
||||
}
|
||||
|
||||
// If no matches, return null
|
||||
return null;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 DEFAULT_GAS = '21000';
|
||||
const DEFAULT_GASPRICE = '20000000000';
|
||||
|
||||
const MAX_GAS_ESTIMATION = '50000000';
|
||||
|
||||
const NULL_ADDRESS = '0000000000000000000000000000000000000000';
|
||||
|
||||
const DOMAIN = '.web3.site';
|
||||
|
||||
export {
|
||||
DEFAULT_GAS,
|
||||
DEFAULT_GASPRICE,
|
||||
MAX_GAS_ESTIMATION,
|
||||
NULL_ADDRESS,
|
||||
DOMAIN
|
||||
};
|
||||
@@ -1,222 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { pick, range, uniq } from 'lodash';
|
||||
|
||||
import { bytesToHex } from '@parity/api/util/format';
|
||||
|
||||
import Contracts from '~/contracts';
|
||||
import builtinJson from '~/config/dappsBuiltin.json';
|
||||
import viewsJson from '~/config/dappsViews.json';
|
||||
import { IconCache } from '~/ui';
|
||||
|
||||
const builtinApps = [].concat(
|
||||
viewsJson.map((app) => {
|
||||
app.isView = true;
|
||||
return app;
|
||||
}),
|
||||
builtinJson.filter((app) => app.id)
|
||||
);
|
||||
|
||||
function getHost (api) {
|
||||
const host = process.env.DAPPS_URL ||
|
||||
(
|
||||
process.env.NODE_ENV === 'production'
|
||||
? api.dappsUrl
|
||||
: ''
|
||||
);
|
||||
|
||||
if (host === '/') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
export function subscribeToChanges (api, dappReg, callback) {
|
||||
return dappReg
|
||||
.getContract()
|
||||
.then((dappRegContract) => {
|
||||
const dappRegInstance = dappRegContract.instance;
|
||||
|
||||
const signatures = ['MetaChanged', 'OwnerChanged', 'Registered']
|
||||
.map((event) => dappRegInstance[event].signature);
|
||||
|
||||
return api.eth
|
||||
.newFilter({
|
||||
fromBlock: '0',
|
||||
toBlock: 'latest',
|
||||
address: dappRegInstance.address,
|
||||
topics: [ signatures ]
|
||||
})
|
||||
.then((filterId) => {
|
||||
return api
|
||||
.subscribe('eth_blockNumber', () => {
|
||||
if (filterId > -1) {
|
||||
api.eth
|
||||
.getFilterChanges(filterId)
|
||||
.then((logs) => {
|
||||
return dappRegContract.parseEventLogs(logs);
|
||||
})
|
||||
.then((events) => {
|
||||
if (events.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Return uniq IDs which changed meta-data
|
||||
const ids = uniq(events.map((event) => bytesToHex(event.params.id.value)));
|
||||
|
||||
callback(ids);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then((blockSubId) => {
|
||||
return {
|
||||
block: blockSubId,
|
||||
filter: filterId
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchBuiltinApps (api) {
|
||||
const { dappReg } = Contracts.get(api);
|
||||
|
||||
return Promise
|
||||
.all(builtinApps.map((app) => dappReg.getImage(app.id)))
|
||||
.then((imageIds) => {
|
||||
return builtinApps.map((app, index) => {
|
||||
app.type = app.isView
|
||||
? 'view'
|
||||
: 'builtin';
|
||||
app.image = IconCache.hashToImage(imageIds[index]);
|
||||
return app;
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('DappsStore:fetchBuiltinApps', error);
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchLocalApps (api) {
|
||||
return fetch(`${getHost(api)}/api/apps`)
|
||||
.then((response) => {
|
||||
return response.ok
|
||||
? response.json()
|
||||
: [];
|
||||
})
|
||||
.then((apps) => {
|
||||
return apps
|
||||
.map((app) => {
|
||||
app.type = 'local';
|
||||
app.visible = true;
|
||||
return app;
|
||||
})
|
||||
.filter((app) => app.id && !['ui'].includes(app.id));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('DappsStore:fetchLocal', error);
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchRegistryAppIds (api) {
|
||||
const { dappReg } = Contracts.get(api);
|
||||
|
||||
return dappReg
|
||||
.count()
|
||||
.then((count) => {
|
||||
const promises = range(0, count.toNumber()).map((index) => dappReg.at(index));
|
||||
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then((appsInfo) => {
|
||||
const appIds = appsInfo
|
||||
.map(([appId, owner]) => bytesToHex(appId))
|
||||
.filter((appId) => {
|
||||
return (new BigNumber(appId)).gt(0) && !builtinApps.find((app) => app.id === appId);
|
||||
});
|
||||
|
||||
return uniq(appIds);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('DappsStore:fetchRegistryAppIds', error);
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchRegistryApp (api, dappReg, appId) {
|
||||
return Promise
|
||||
.all([
|
||||
dappReg.getImage(appId),
|
||||
dappReg.getContent(appId),
|
||||
dappReg.getManifest(appId)
|
||||
])
|
||||
.then(([ imageId, contentId, manifestId ]) => {
|
||||
const app = {
|
||||
id: appId,
|
||||
image: IconCache.hashToImage(imageId),
|
||||
contentHash: bytesToHex(contentId).substr(2),
|
||||
manifestHash: bytesToHex(manifestId).substr(2),
|
||||
type: 'network',
|
||||
visible: true
|
||||
};
|
||||
|
||||
return fetchManifest(api, app.manifestHash)
|
||||
.then((manifest) => {
|
||||
if (manifest) {
|
||||
app.manifestHash = null;
|
||||
|
||||
// Add usefull manifest fields to app
|
||||
Object.assign(app, pick(manifest, ['author', 'description', 'name', 'version']));
|
||||
}
|
||||
|
||||
return app;
|
||||
});
|
||||
})
|
||||
.then((app) => {
|
||||
// Keep dapps that has a Manifest File and an Id
|
||||
const dapp = (app.manifestHash || !app.id) ? null : app;
|
||||
|
||||
return dapp;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('DappsStore:fetchRegistryApp', error);
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchManifest (api, manifestHash) {
|
||||
if (/^(0x)?0+/.test(manifestHash)) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return fetch(
|
||||
`${getHost(api)}/api/content/${manifestHash}/`,
|
||||
{ redirect: 'follow', mode: 'cors' }
|
||||
)
|
||||
.then((response) => {
|
||||
return response.ok
|
||||
? response.json()
|
||||
: null;
|
||||
})
|
||||
.then((manifest) => {
|
||||
return manifest;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('DappsStore:fetchManifest', error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 Push from 'push.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import unkownIcon from '~/../assets/images/contracts/unknown-64x64.png';
|
||||
|
||||
export function notifyTransaction (account, token, _value, onClick) {
|
||||
const name = account.name || account.address;
|
||||
const value = _value.div(new BigNumber(token.format || 1));
|
||||
const icon = token.image || unkownIcon;
|
||||
|
||||
let _notification = null;
|
||||
|
||||
Push
|
||||
.create(`${name}`, {
|
||||
body: `You just received ${value.toFormat(3)} ${token.tag.toUpperCase()}`,
|
||||
icon: {
|
||||
x16: icon,
|
||||
x32: icon
|
||||
},
|
||||
timeout: 20000,
|
||||
onClick: () => {
|
||||
// Focus on the UI
|
||||
try {
|
||||
window.focus();
|
||||
} catch (e) {}
|
||||
|
||||
if (onClick && typeof onClick === 'function') {
|
||||
onClick();
|
||||
}
|
||||
|
||||
// Close the notification
|
||||
if (_notification) {
|
||||
_notification.close();
|
||||
_notification = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((notification) => {
|
||||
_notification = notification;
|
||||
});
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { PropTypes } from 'react';
|
||||
|
||||
export function arrayOrObjectProptype () {
|
||||
return PropTypes.oneOfType([
|
||||
PropTypes.array,
|
||||
PropTypes.object
|
||||
]);
|
||||
}
|
||||
|
||||
export function nullableProptype (type) {
|
||||
return PropTypes.oneOfType([
|
||||
PropTypes.oneOf([ null ]),
|
||||
type
|
||||
]);
|
||||
}
|
||||
|
||||
export function nodeOrStringProptype () {
|
||||
return PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.string
|
||||
]);
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 scrypt from 'scryptsy';
|
||||
import Transaction from 'ethereumjs-tx';
|
||||
import { pbkdf2Sync } from 'crypto';
|
||||
import { createDecipheriv } from 'browserify-aes';
|
||||
|
||||
import { inHex } from '@parity/api/format/input';
|
||||
import { sha3 } from '@parity/api/util/sha3';
|
||||
|
||||
// Adapted from https://github.com/kvhnuke/etherwallet/blob/mercury/app/scripts/myetherwallet.js
|
||||
|
||||
export class Signer {
|
||||
static fromJson (json, password) {
|
||||
return Signer
|
||||
.getSeed(json, password)
|
||||
.then((seed) => {
|
||||
return new Signer(seed);
|
||||
});
|
||||
}
|
||||
|
||||
static getSeed (json, password) {
|
||||
try {
|
||||
const seed = Signer.getSyncSeed(json, password);
|
||||
|
||||
return Promise.resolve(seed);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
static getSyncSeed (json, password) {
|
||||
if (json.version !== 3) {
|
||||
throw new Error('Only V3 wallets are supported');
|
||||
}
|
||||
|
||||
const { kdf } = json.crypto;
|
||||
const kdfparams = json.crypto.kdfparams || {};
|
||||
const pwd = Buffer.from(password);
|
||||
const salt = Buffer.from(kdfparams.salt, 'hex');
|
||||
let derivedKey;
|
||||
|
||||
if (kdf === 'scrypt') {
|
||||
derivedKey = scrypt(pwd, salt, kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen);
|
||||
} else if (kdf === 'pbkdf2') {
|
||||
if (kdfparams.prf !== 'hmac-sha256') {
|
||||
throw new Error('Unsupported parameters to PBKDF2');
|
||||
}
|
||||
|
||||
derivedKey = pbkdf2Sync(pwd, salt, kdfparams.c, kdfparams.dklen, 'sha256');
|
||||
} else {
|
||||
throw new Error('Unsupported key derivation scheme');
|
||||
}
|
||||
|
||||
const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex');
|
||||
const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
|
||||
|
||||
if (mac !== inHex(json.crypto.mac)) {
|
||||
throw new Error('Key derivation failed - possibly wrong password');
|
||||
}
|
||||
|
||||
const decipher = createDecipheriv(
|
||||
json.crypto.cipher,
|
||||
derivedKey.slice(0, 16),
|
||||
Buffer.from(json.crypto.cipherparams.iv, 'hex')
|
||||
);
|
||||
|
||||
let seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
||||
|
||||
while (seed.length < 32) {
|
||||
const nullBuff = Buffer.from([0x00]);
|
||||
|
||||
seed = Buffer.concat([nullBuff, seed]);
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
||||
constructor (seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
signTransactionObject (tx) {
|
||||
tx.sign(this.seed);
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
signTransaction (transaction) {
|
||||
const tx = new Transaction(transaction);
|
||||
|
||||
return inHex(this.signTransactionObject(tx).serialize().toString('hex'));
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 solc from 'solc/browser-wrapper';
|
||||
|
||||
export default class SolidityUtils {
|
||||
static compile (data, compiler) {
|
||||
const { sourcecode, build, optimize, files, name = '' } = data;
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
console.log('[solidity] compiling...');
|
||||
|
||||
const input = {
|
||||
[ name ]: sourcecode
|
||||
};
|
||||
|
||||
const findFiles = (path) => {
|
||||
const file = files.find((f) => f.name === path);
|
||||
|
||||
if (file) {
|
||||
return { contents: file.sourcecode };
|
||||
} else {
|
||||
return { error: 'File not found' };
|
||||
}
|
||||
};
|
||||
|
||||
const compiled = compiler.compile({ sources: input }, optimize ? 1 : 0, findFiles);
|
||||
|
||||
const time = Math.round((Date.now() - start) / 100) / 10;
|
||||
|
||||
console.log(`[solidity] done compiling in ${time}s`);
|
||||
|
||||
compiled.version = build.longVersion;
|
||||
compiled.sourcecode = sourcecode;
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
static getCompiler (build) {
|
||||
const { longVersion, path } = build;
|
||||
const URL = `https://cdn.rawgit.com/ethereum/solc-bin/gh-pages/bin/${path}`;
|
||||
const isWorker = typeof window !== 'object';
|
||||
|
||||
return fetch(URL)
|
||||
.then((r) => r.text())
|
||||
.then((code) => {
|
||||
// `window` for main thread, `self` for workers
|
||||
const _self = isWorker ? self : window;
|
||||
|
||||
_self.Module = {};
|
||||
|
||||
const solcCode = code.replace('var Module;', `var Module=${isWorker ? 'self' : 'window'}.Module;`);
|
||||
|
||||
console.log(`[solidity] evaluating ${longVersion}`);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(solcCode);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
console.log(`[solidity] done evaluating ${longVersion}`);
|
||||
|
||||
const compiler = solc(_self.Module);
|
||||
|
||||
delete _self.Module;
|
||||
|
||||
return compiler;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 EventEmitter from 'eventemitter3';
|
||||
|
||||
const defaults = {
|
||||
from: 0,
|
||||
to: 'latest',
|
||||
interval: 5000,
|
||||
filter: () => true
|
||||
};
|
||||
|
||||
const subscribeToEvents = (contract, events, opt = {}) => {
|
||||
const { api } = contract;
|
||||
|
||||
opt = Object.assign({}, defaults, opt);
|
||||
|
||||
let filter = null;
|
||||
let interval = null;
|
||||
|
||||
const unsubscribe = () => {
|
||||
if (filter) {
|
||||
filter
|
||||
.then((filterId) => {
|
||||
return api.eth.uninstallFilter(filterId);
|
||||
})
|
||||
.catch((err) => {
|
||||
emitter.emit('error', err);
|
||||
});
|
||||
filter = null;
|
||||
}
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
}
|
||||
};
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.unsubscribe = unsubscribe;
|
||||
|
||||
const fetcher = (method, filterId) => () => {
|
||||
api
|
||||
.eth[method](filterId)
|
||||
.then((logs) => {
|
||||
logs = contract.parseEventLogs(logs);
|
||||
|
||||
for (let log of logs) {
|
||||
if (opt.filter(log)) {
|
||||
emitter.emit('log', log);
|
||||
emitter.emit(log.event, log);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
emitter.emit('error', err);
|
||||
});
|
||||
};
|
||||
|
||||
const signatures = events
|
||||
.filter((event) => contract.instance[event])
|
||||
.map((event) => contract.instance[event].signature);
|
||||
|
||||
filter = api.eth
|
||||
.newFilter({
|
||||
fromBlock: opt.from,
|
||||
toBlock: opt.to,
|
||||
address: contract.address,
|
||||
topics: [signatures]
|
||||
})
|
||||
.then((filterId) => {
|
||||
fetcher('getFilterLogs', filterId)(); // fetch immediately
|
||||
|
||||
const fetchChanges = fetcher('getFilterChanges', filterId);
|
||||
|
||||
interval = setInterval(fetchChanges, opt.interval);
|
||||
|
||||
return filterId;
|
||||
})
|
||||
.catch((err) => {
|
||||
emitter.emit('error', err);
|
||||
throw err; // reject Promise
|
||||
});
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
export default subscribeToEvents;
|
||||
@@ -1,137 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { spy, stub, useFakeTimers } from 'sinon';
|
||||
|
||||
import subscribeToEvents from './subscribe-to-events';
|
||||
import {
|
||||
pastLogs, liveLogs, createApi, createContract
|
||||
} from './subscribe-to-events.test.js';
|
||||
|
||||
// Note: We want to have a `setTimeout` that is independent from
|
||||
// `sinon.useFakeTimers`. Therefore we dereference `setTimeout` here.
|
||||
const _setTimeout = setTimeout;
|
||||
const delay = (t) => new Promise((resolve) => {
|
||||
_setTimeout(resolve, t);
|
||||
});
|
||||
|
||||
describe('util/subscribe-to-events', () => {
|
||||
beforeEach(function () {
|
||||
this.api = createApi();
|
||||
this.contract = createContract(this.api);
|
||||
});
|
||||
|
||||
it('installs a filter', async function () {
|
||||
const { api, contract } = this;
|
||||
|
||||
subscribeToEvents(contract, [ 'Foo', 'Bar' ]);
|
||||
await delay(0);
|
||||
|
||||
expect(api.eth.newFilter.calledOnce).to.equal(true);
|
||||
expect(api.eth.newFilter.firstCall.args).to.eql([ {
|
||||
fromBlock: 0, toBlock: 'latest',
|
||||
address: contract.address,
|
||||
topics: [ [
|
||||
contract.instance.Foo.signature,
|
||||
contract.instance.Bar.signature
|
||||
] ]
|
||||
} ]);
|
||||
});
|
||||
|
||||
it('queries & parses logs in the beginning', async function () {
|
||||
const { api, contract } = this;
|
||||
|
||||
subscribeToEvents(contract, [ 'Foo', 'Bar' ]);
|
||||
|
||||
await delay(0);
|
||||
expect(api.eth.getFilterLogs.callCount).to.equal(1);
|
||||
expect(api.eth.getFilterLogs.firstCall.args).to.eql([ 123 ]);
|
||||
|
||||
await delay(0);
|
||||
expect(contract.parseEventLogs.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('emits logs in the beginning', async function () {
|
||||
const { contract } = this;
|
||||
|
||||
const onLog = spy();
|
||||
const onFoo = spy();
|
||||
const onBar = spy();
|
||||
|
||||
subscribeToEvents(contract, [ 'Foo', 'Bar' ])
|
||||
.on('log', onLog)
|
||||
.on('Foo', onFoo)
|
||||
.on('Bar', onBar);
|
||||
|
||||
await delay(0);
|
||||
|
||||
expect(onLog.callCount).to.equal(2);
|
||||
expect(onLog.firstCall.args).to.eql([ pastLogs[0] ]);
|
||||
expect(onLog.secondCall.args).to.eql([ pastLogs[1] ]);
|
||||
expect(onFoo.callCount).to.equal(1);
|
||||
expect(onFoo.firstCall.args).to.eql([ pastLogs[0] ]);
|
||||
expect(onBar.callCount).to.equal(1);
|
||||
expect(onBar.firstCall.args).to.eql([ pastLogs[1] ]);
|
||||
});
|
||||
|
||||
it('uninstalls the filter on sunsubscribe', async function () {
|
||||
const { api, contract } = this;
|
||||
|
||||
const s = subscribeToEvents(contract, [ 'Foo', 'Bar' ]);
|
||||
|
||||
await delay(0);
|
||||
s.unsubscribe();
|
||||
await delay(0);
|
||||
|
||||
expect(api.eth.uninstallFilter.calledOnce).to.equal(true);
|
||||
expect(api.eth.uninstallFilter.firstCall.args).to.eql([ 123 ]);
|
||||
});
|
||||
|
||||
it('checks for new events regularly', async function () {
|
||||
const clock = useFakeTimers();
|
||||
const { api, contract } = this;
|
||||
|
||||
api.eth.getFilterLogs = stub().resolves([]);
|
||||
|
||||
const onLog = spy();
|
||||
const onBar = spy();
|
||||
|
||||
subscribeToEvents(contract, [ 'Bar' ], { interval: 5 })
|
||||
.on('log', onLog)
|
||||
.on('Bar', onBar);
|
||||
await delay(1); // let stubs resolve
|
||||
clock.tick(5);
|
||||
await delay(1); // let stubs resolve
|
||||
|
||||
expect(onLog.callCount).to.be.at.least(1);
|
||||
expect(onLog.firstCall.args).to.eql([ liveLogs[0] ]);
|
||||
expect(onBar.callCount).to.be.at.least(1);
|
||||
expect(onBar.firstCall.args).to.eql([ liveLogs[0] ]);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('accepts a custom block range', async function () {
|
||||
const { api, contract } = this;
|
||||
|
||||
subscribeToEvents(contract, [ 'Foo' ], { from: 123, to: 321 });
|
||||
|
||||
await delay(0);
|
||||
expect(api.eth.newFilter.callCount).to.equal(1);
|
||||
expect(api.eth.newFilter.firstCall.args[0].fromBlock).to.equal(123);
|
||||
expect(api.eth.newFilter.firstCall.args[0].toBlock).to.equal(321);
|
||||
});
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { stub } from 'sinon';
|
||||
|
||||
export const ADDRESS = '0x1111111111111111111111111111111111111111';
|
||||
|
||||
export const pastLogs = [
|
||||
{ event: 'Foo', type: 'mined', address: ADDRESS, params: {} },
|
||||
{ event: 'Bar', type: 'mined', address: ADDRESS, params: {} }
|
||||
];
|
||||
|
||||
export const liveLogs = [
|
||||
{ event: 'Bar', type: 'mined', address: ADDRESS, params: { foo: 'bar' } }
|
||||
];
|
||||
|
||||
export const createApi = () => ({
|
||||
eth: {
|
||||
newFilter: stub().resolves(123),
|
||||
uninstallFilter: stub()
|
||||
.rejects(new Error('unknown filter id'))
|
||||
.withArgs(123).resolves(null),
|
||||
getFilterLogs: stub()
|
||||
.rejects(new Error('unknown filter id'))
|
||||
.withArgs(123).resolves(pastLogs),
|
||||
getFilterChanges: stub()
|
||||
.rejects(new Error('unknown filter id'))
|
||||
.withArgs(123).resolves(liveLogs)
|
||||
}
|
||||
});
|
||||
|
||||
export const createContract = (api) => ({
|
||||
api,
|
||||
address: ADDRESS,
|
||||
instance: {
|
||||
Foo: { signature: 'Foo signature' },
|
||||
Bar: { signature: 'Bar signature' }
|
||||
},
|
||||
parseEventLogs: stub().returnsArg(0)
|
||||
});
|
||||
@@ -1,134 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { range } from 'lodash';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import { sha3 } from '@parity/api/util/sha3';
|
||||
|
||||
import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
||||
import { IconCache } from '~/ui';
|
||||
|
||||
const BALANCEOF_SIGNATURE = sha3('balanceOf(address)');
|
||||
const ADDRESS_PADDING = range(24).map(() => '0').join('');
|
||||
|
||||
export const ETH_TOKEN = {
|
||||
address: '',
|
||||
format: new BigNumber(10).pow(18),
|
||||
id: sha3('eth_native_token').slice(0, 10),
|
||||
image: imagesEthereum,
|
||||
name: 'Ethereum',
|
||||
native: true,
|
||||
tag: 'ETH'
|
||||
};
|
||||
|
||||
export function fetchTokenIds (tokenregInstance) {
|
||||
return tokenregInstance.tokenCount
|
||||
.call()
|
||||
.then((numTokens) => {
|
||||
const tokenIndexes = range(numTokens.toNumber());
|
||||
|
||||
return tokenIndexes;
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchTokenInfo (api, tokenregInstace, tokenIndex) {
|
||||
return Promise
|
||||
.all([
|
||||
tokenregInstace.token.call({}, [tokenIndex]),
|
||||
tokenregInstace.meta.call({}, [tokenIndex, 'IMG'])
|
||||
])
|
||||
.then(([ tokenData, image ]) => {
|
||||
const [ address, tag, format, name ] = tokenData;
|
||||
|
||||
const token = {
|
||||
format: format.toString(),
|
||||
index: tokenIndex,
|
||||
image: IconCache.hashToImage(image),
|
||||
id: sha3(address + tokenIndex).slice(0, 10),
|
||||
address,
|
||||
name,
|
||||
tag
|
||||
};
|
||||
|
||||
return token;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* `updates` should be in the shape:
|
||||
* {
|
||||
* [ who ]: [ tokenId ] // Array of tokens to updates
|
||||
* }
|
||||
*
|
||||
* Returns a Promise resolved witht the balances in the shape:
|
||||
* {
|
||||
* [ who ]: { [ tokenId ]: BigNumber } // The balances of `who`
|
||||
* }
|
||||
*/
|
||||
export function fetchAccountsBalances (api, tokens, updates) {
|
||||
const addresses = Object.keys(updates);
|
||||
const promises = addresses
|
||||
.map((who) => {
|
||||
const tokensIds = updates[who];
|
||||
const tokensToUpdate = tokensIds.map((tokenId) => tokens.find((t) => t.id === tokenId));
|
||||
|
||||
return fetchAccountBalances(api, tokensToUpdate, who);
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then((results) => {
|
||||
return results.reduce((balances, accountBalances, index) => {
|
||||
balances[addresses[index]] = accountBalances;
|
||||
return balances;
|
||||
}, {});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise resolved with the balances in the shape:
|
||||
* {
|
||||
* [ tokenId ]: BigNumber // Token balance value
|
||||
* }
|
||||
*/
|
||||
export function fetchAccountBalances (api, tokens, who) {
|
||||
const calldata = '0x' + BALANCEOF_SIGNATURE.slice(2, 10) + ADDRESS_PADDING + who.slice(2);
|
||||
const promises = tokens.map((token) => fetchTokenBalance(api, token, { who, calldata }));
|
||||
|
||||
return Promise.all(promises)
|
||||
.then((results) => {
|
||||
return results.reduce((balances, value, index) => {
|
||||
const token = tokens[index];
|
||||
|
||||
balances[token.id] = value;
|
||||
return balances;
|
||||
}, {});
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchTokenBalance (api, token, { who, calldata }) {
|
||||
if (token.native) {
|
||||
return api.eth.getBalance(who);
|
||||
}
|
||||
|
||||
return api.eth
|
||||
.call({ data: calldata, to: token.address })
|
||||
.then((result) => {
|
||||
const cleanResult = result.replace(/^0x/, '');
|
||||
|
||||
return new BigNumber(`0x${cleanResult || 0}`);
|
||||
});
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 WalletsUtils from '~/util/wallets';
|
||||
|
||||
export function trackRequest (api, options, statusCallback) {
|
||||
const { requestId, transactionHash } = options;
|
||||
const txHashPromise = transactionHash
|
||||
? Promise.resolve(transactionHash)
|
||||
: api.pollMethod('parity_checkRequest', requestId);
|
||||
|
||||
return txHashPromise
|
||||
.then((transactionHash) => {
|
||||
statusCallback(null, { transactionHash });
|
||||
return api.pollMethod('eth_getTransactionReceipt', transactionHash, isValidReceipt);
|
||||
})
|
||||
.then((transactionReceipt) => {
|
||||
statusCallback(null, { transactionReceipt });
|
||||
})
|
||||
.catch((error) => {
|
||||
statusCallback(error);
|
||||
});
|
||||
}
|
||||
|
||||
const isValidReceipt = (receipt) => {
|
||||
return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
|
||||
};
|
||||
|
||||
function getTxArgs (func, options, values = []) {
|
||||
const { contract } = func;
|
||||
const { api } = contract;
|
||||
const address = options.from;
|
||||
|
||||
if (!address) {
|
||||
return Promise.resolve({ func, options, values });
|
||||
}
|
||||
|
||||
return WalletsUtils
|
||||
.isWallet(api, address)
|
||||
.then((isWallet) => {
|
||||
if (!isWallet) {
|
||||
return { func, options, values };
|
||||
}
|
||||
|
||||
options.data = contract.getCallData(func, options, values);
|
||||
options.to = options.to || contract.address;
|
||||
|
||||
if (!options.to) {
|
||||
return { func, options, values };
|
||||
}
|
||||
|
||||
return WalletsUtils
|
||||
.getCallArgs(api, options, values)
|
||||
.then((callArgs) => {
|
||||
if (!callArgs) {
|
||||
return { func, options, values };
|
||||
}
|
||||
|
||||
return callArgs;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function estimateGas (_func, _options, _values = []) {
|
||||
return getTxArgs(_func, _options, _values)
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
|
||||
return func._estimateGas(options, values);
|
||||
});
|
||||
}
|
||||
|
||||
export function postTransaction (_func, _options, _values = []) {
|
||||
return getTxArgs(_func, _options, _values)
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
|
||||
return func._postTransaction(options, values);
|
||||
});
|
||||
}
|
||||
|
||||
export function deployEstimateGas (contract, _options, values) {
|
||||
const options = { ..._options };
|
||||
const { api } = contract;
|
||||
const address = options.from;
|
||||
|
||||
return WalletsUtils
|
||||
.isWallet(api, address)
|
||||
.then((isWallet) => {
|
||||
if (!isWallet) {
|
||||
return contract.deployEstimateGas(options, values);
|
||||
}
|
||||
|
||||
return WalletsUtils
|
||||
.getDeployArgs(contract, options, values)
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
|
||||
return func.estimateGas(options, values);
|
||||
})
|
||||
.then((gasEst) => {
|
||||
return [gasEst, gasEst.mul(1.05)];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function deploy (contract, options, values, skipGasEstimate = false) {
|
||||
const { api } = contract;
|
||||
const address = options.from;
|
||||
|
||||
const gasEstPromise = skipGasEstimate
|
||||
? Promise.resolve(null)
|
||||
: deployEstimateGas(contract, options, values).then(([gasEst, gas]) => gas);
|
||||
|
||||
return gasEstPromise
|
||||
.then((gas) => {
|
||||
if (gas) {
|
||||
options.gas = gas.toFixed(0);
|
||||
}
|
||||
|
||||
return WalletsUtils.isWallet(api, address);
|
||||
})
|
||||
.then((isWallet) => {
|
||||
if (!isWallet) {
|
||||
const encodedOptions = contract._encodeOptions(contract.constructors[0], options, values);
|
||||
|
||||
return api.parity.postTransaction(encodedOptions);
|
||||
}
|
||||
|
||||
return WalletsUtils.getDeployArgs(contract, options, values)
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
|
||||
return func._postTransaction(options, values);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function parseTransactionReceipt (api, options, receipt) {
|
||||
const { metadata } = options;
|
||||
const address = options.from;
|
||||
|
||||
if (receipt.gasUsed.eq(options.gas)) {
|
||||
const error = new Error(`Contract not deployed, gasUsed == ${options.gas.toFixed(0)}`);
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const logs = WalletsUtils.parseLogs(api, receipt.logs || []);
|
||||
|
||||
const confirmationLog = logs.find((log) => log.event === 'ConfirmationNeeded');
|
||||
const transactionLog = logs.find((log) => log.event === 'SingleTransact');
|
||||
|
||||
if (!confirmationLog && !transactionLog && !receipt.contractAddress) {
|
||||
const error = new Error('Something went wrong in the contract deployment...');
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// Confirmations are needed from the other owners
|
||||
if (confirmationLog) {
|
||||
const operationHash = api.util.bytesToHex(confirmationLog.params.operation.value);
|
||||
|
||||
// Add the contract to pending contracts
|
||||
WalletsUtils.addPendingContract(address, operationHash, metadata);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (transactionLog) {
|
||||
// Set the contract address in the receipt
|
||||
receipt.contractAddress = transactionLog.params.created.value;
|
||||
}
|
||||
|
||||
const contractAddress = receipt.contractAddress;
|
||||
|
||||
return api.eth
|
||||
.getCode(contractAddress)
|
||||
.then((code) => {
|
||||
if (code === '0x') {
|
||||
throw new Error('Contract not deployed, getCode returned 0x');
|
||||
}
|
||||
|
||||
return contractAddress;
|
||||
});
|
||||
}
|
||||
|
||||
export function patchApi (api) {
|
||||
api.patch = {
|
||||
...api.patch,
|
||||
contract: patchContract
|
||||
};
|
||||
}
|
||||
|
||||
export function patchContract (contract) {
|
||||
contract._functions.forEach((func) => {
|
||||
if (!func.constant) {
|
||||
func._postTransaction = func.postTransaction;
|
||||
func._estimateGas = func.estimateGas;
|
||||
|
||||
func.postTransaction = postTransaction.bind(contract, func);
|
||||
func.estimateGas = estimateGas.bind(contract, func);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function checkIfTxFailed (api, tx, gasSent) {
|
||||
return api.pollMethod('eth_getTransactionReceipt', tx)
|
||||
.then((receipt) => {
|
||||
// TODO: Right now, there's no way to tell wether the EVM code crashed.
|
||||
// Because you usually send a bit more gas than estimated (to make sure
|
||||
// it gets mined quickly), we transaction probably failed if all the gas
|
||||
// has been used up.
|
||||
return receipt.gasUsed.eq(gasSent);
|
||||
});
|
||||
}
|
||||
|
||||
export function waitForConfirmations (api, tx, confirmations) {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.pollMethod('eth_getTransactionReceipt', tx, isValidReceipt)
|
||||
.then((receipt) => {
|
||||
let subscription;
|
||||
|
||||
api.subscribe('eth_blockNumber', (err, block) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (block.minus(confirmations - 1).gte(receipt.blockNumber)) {
|
||||
if (subscription) {
|
||||
api.unsubscribe(subscription);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.then((_subscription) => {
|
||||
subscription = _subscription;
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 apiutil from '@parity/api/util';
|
||||
|
||||
import { NULL_ADDRESS } from './constants';
|
||||
|
||||
// TODO: Convert to FormattedMessages as soon as comfortable with the impact, i.e. errors
|
||||
// not being concatted into strings in components, all supporting a non-string format
|
||||
export const ERRORS = {
|
||||
invalidAddress: 'address is an invalid network address',
|
||||
invalidAmount: 'the supplied amount should be a valid positive number',
|
||||
invalidAmountDecimals: 'the supplied amount exceeds the allowed decimals',
|
||||
duplicateAddress: 'the address is already in your address book',
|
||||
invalidChecksum: 'address has failed the checksum formatting',
|
||||
invalidName: 'name should not be blank and longer than 2',
|
||||
invalidAbi: 'abi should be a valid JSON array',
|
||||
invalidCode: 'code should be the compiled hex string',
|
||||
invalidNumber: 'invalid number format',
|
||||
negativeNumber: 'input number should be positive',
|
||||
decimalNumber: 'input number should not contain decimals',
|
||||
gasException: 'the transaction will throw an exception with the current values',
|
||||
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
|
||||
};
|
||||
|
||||
export function validateAbi (abi) {
|
||||
let abiError = null;
|
||||
let abiParsed = null;
|
||||
|
||||
try {
|
||||
abiParsed = JSON.parse(abi);
|
||||
|
||||
if (!apiutil.isArray(abiParsed)) {
|
||||
abiError = ERRORS.invalidAbi;
|
||||
|
||||
return {
|
||||
error: abiError,
|
||||
abi,
|
||||
abiError,
|
||||
abiParsed
|
||||
};
|
||||
}
|
||||
|
||||
// Validate each elements of the Array
|
||||
const invalidIndex = abiParsed
|
||||
.map((o) => isValidAbiEvent(o) || isValidAbiFunction(o) || isAbiFallback(o))
|
||||
.findIndex((valid) => !valid);
|
||||
|
||||
if (invalidIndex !== -1) {
|
||||
const invalid = abiParsed[invalidIndex];
|
||||
|
||||
// TODO: Needs seperate error when using FormattedMessage (no concats)
|
||||
abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`;
|
||||
|
||||
return {
|
||||
error: abiError,
|
||||
abi,
|
||||
abiError,
|
||||
abiParsed
|
||||
};
|
||||
}
|
||||
|
||||
abi = JSON.stringify(abiParsed);
|
||||
} catch (error) {
|
||||
abiError = ERRORS.invalidAbi;
|
||||
}
|
||||
|
||||
return {
|
||||
error: abiError,
|
||||
abi,
|
||||
abiError,
|
||||
abiParsed
|
||||
};
|
||||
}
|
||||
|
||||
function isValidAbiFunction (object) {
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((object.type === 'function' && object.name) || object.type === 'constructor') &&
|
||||
(object.inputs && apiutil.isArray(object.inputs));
|
||||
}
|
||||
|
||||
function isAbiFallback (object) {
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return object.type === 'fallback';
|
||||
}
|
||||
|
||||
function isValidAbiEvent (object) {
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (object.type === 'event') &&
|
||||
(object.name) &&
|
||||
(object.inputs && apiutil.isArray(object.inputs));
|
||||
}
|
||||
|
||||
export function validateAddress (address) {
|
||||
let addressError = null;
|
||||
|
||||
if (!address) {
|
||||
addressError = ERRORS.invalidAddress;
|
||||
} else if (!apiutil.isAddressValid(address)) {
|
||||
addressError = ERRORS.invalidAddress;
|
||||
} else {
|
||||
address = apiutil.toChecksumAddress(address);
|
||||
}
|
||||
|
||||
return {
|
||||
error: addressError,
|
||||
address,
|
||||
addressError
|
||||
};
|
||||
}
|
||||
|
||||
export function validateCode (code) {
|
||||
let codeError = null;
|
||||
|
||||
if (!code || !code.length) {
|
||||
codeError = ERRORS.invalidCode;
|
||||
} else if (!apiutil.isHex(code)) {
|
||||
codeError = ERRORS.invalidCode;
|
||||
}
|
||||
|
||||
return {
|
||||
error: codeError,
|
||||
code,
|
||||
codeError
|
||||
};
|
||||
}
|
||||
|
||||
export function validateName (name) {
|
||||
const nameError = !name || name.trim().length < 2
|
||||
? ERRORS.invalidName
|
||||
: null;
|
||||
|
||||
return {
|
||||
error: nameError,
|
||||
name,
|
||||
nameError
|
||||
};
|
||||
}
|
||||
|
||||
export function validatePositiveNumber (number) {
|
||||
let numberError = null;
|
||||
|
||||
try {
|
||||
const v = new BigNumber(number);
|
||||
|
||||
if (v.lt(0)) {
|
||||
numberError = ERRORS.invalidAmount;
|
||||
}
|
||||
} catch (e) {
|
||||
numberError = ERRORS.invalidAmount;
|
||||
}
|
||||
|
||||
return {
|
||||
error: numberError,
|
||||
number,
|
||||
numberError
|
||||
};
|
||||
}
|
||||
|
||||
export function validateDecimalsNumber (number, base = 1) {
|
||||
let numberError = null;
|
||||
|
||||
try {
|
||||
const s = new BigNumber(number).mul(base).toFixed();
|
||||
|
||||
if (s.indexOf('.') !== -1) {
|
||||
numberError = ERRORS.invalidAmountDecimals;
|
||||
}
|
||||
} catch (e) {
|
||||
numberError = ERRORS.invalidAmount;
|
||||
}
|
||||
|
||||
return {
|
||||
error: numberError,
|
||||
number,
|
||||
numberError
|
||||
};
|
||||
}
|
||||
|
||||
export function validateUint (value) {
|
||||
let valueError = null;
|
||||
|
||||
try {
|
||||
const bn = new BigNumber(value);
|
||||
|
||||
if (bn.lt(0)) {
|
||||
valueError = ERRORS.negativeNumber;
|
||||
} else if (!bn.isInteger()) {
|
||||
valueError = ERRORS.decimalNumber;
|
||||
}
|
||||
} catch (e) {
|
||||
valueError = ERRORS.invalidNumber;
|
||||
}
|
||||
|
||||
return {
|
||||
error: valueError,
|
||||
value,
|
||||
valueError
|
||||
};
|
||||
}
|
||||
|
||||
export function isNullAddress (address) {
|
||||
if (address && address.substr(0, 2) === '0x') {
|
||||
return isNullAddress(address.substr(2));
|
||||
}
|
||||
|
||||
return address === NULL_ADDRESS;
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { NULL_ADDRESS } from './constants';
|
||||
import { ERRORS, isNullAddress, validateAbi, validateAddress, validateCode, validateName, validatePositiveNumber, validateUint } from './validation';
|
||||
|
||||
describe('util/validation', () => {
|
||||
describe('validateAbi', () => {
|
||||
it('passes on valid ABI', () => {
|
||||
const abi = '[{"type":"function","name":"test","inputs":[],"outputs":[]}]';
|
||||
|
||||
expect(validateAbi(abi)).to.deep.equal({
|
||||
abi,
|
||||
abiError: null,
|
||||
abiParsed: [{
|
||||
type: 'function',
|
||||
name: 'test',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
}],
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('passes on valid ABI & trims ABI', () => {
|
||||
const abi = '[ { "type" : "function" , "name" : "test" , "inputs" : [] , "outputs" : [] } ]';
|
||||
|
||||
expect(validateAbi(abi)).to.deep.equal({
|
||||
abi: '[{"type":"function","name":"test","inputs":[],"outputs":[]}]',
|
||||
abiError: null,
|
||||
abiParsed: [{
|
||||
type: 'function',
|
||||
name: 'test',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
}],
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on invalid JSON', () => {
|
||||
const abi = 'this is not json';
|
||||
|
||||
expect(validateAbi(abi)).to.deep.equal({
|
||||
abi,
|
||||
abiError: ERRORS.invalidAbi,
|
||||
abiParsed: null,
|
||||
error: ERRORS.invalidAbi
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on non-array JSON', () => {
|
||||
const abi = '{}';
|
||||
|
||||
expect(validateAbi(abi)).to.deep.equal({
|
||||
abi,
|
||||
abiError: ERRORS.invalidAbi,
|
||||
abiParsed: {},
|
||||
error: ERRORS.invalidAbi
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with invalid event', () => {
|
||||
const abi = '[{ "type":"event" }]';
|
||||
|
||||
expect(validateAbi(abi)).to.deep.equal({
|
||||
abi,
|
||||
abiError: `${ERRORS.invalidAbi} (#0: event)`,
|
||||
abiParsed: [{ type: 'event' }],
|
||||
error: `${ERRORS.invalidAbi} (#0: event)`
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with invalid function', () => {
|
||||
const abi = '[{ "type":"function" }]';
|
||||
|
||||
expect(validateAbi(abi)).to.deep.equal({
|
||||
abi,
|
||||
abiError: `${ERRORS.invalidAbi} (#0: function)`,
|
||||
abiParsed: [{ type: 'function' }],
|
||||
error: `${ERRORS.invalidAbi} (#0: function)`
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with unknown type', () => {
|
||||
const abi = '[{ "type":"somethingElse" }]';
|
||||
|
||||
expect(validateAbi(abi)).to.deep.equal({
|
||||
abi,
|
||||
abiError: `${ERRORS.invalidAbi} (#0: somethingElse)`,
|
||||
abiParsed: [{ type: 'somethingElse' }],
|
||||
error: `${ERRORS.invalidAbi} (#0: somethingElse)`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateAddress', () => {
|
||||
it('validates address', () => {
|
||||
const address = '0x1234567890123456789012345678901234567890';
|
||||
|
||||
expect(validateAddress(address)).to.deep.equal({
|
||||
address,
|
||||
addressError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('validates address and converts to checksum', () => {
|
||||
const address = '0x5A5eFF38DA95b0D58b6C616f2699168B480953C9';
|
||||
|
||||
expect(validateAddress(address.toLowerCase())).to.deep.equal({
|
||||
address,
|
||||
addressError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on null addresses', () => {
|
||||
expect(validateAddress(null)).to.deep.equal({
|
||||
address: null,
|
||||
addressError: ERRORS.invalidAddress,
|
||||
error: ERRORS.invalidAddress
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on invalid addresses', () => {
|
||||
const address = '0x12344567';
|
||||
|
||||
expect(validateAddress(address)).to.deep.equal({
|
||||
address,
|
||||
addressError: ERRORS.invalidAddress,
|
||||
error: ERRORS.invalidAddress
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateCode', () => {
|
||||
it('validates hex code', () => {
|
||||
expect(validateCode('0x123abc')).to.deep.equal({
|
||||
code: '0x123abc',
|
||||
codeError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('validates hex code (non-prefix)', () => {
|
||||
expect(validateCode('123abc')).to.deep.equal({
|
||||
code: '123abc',
|
||||
codeError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on invalid code', () => {
|
||||
expect(validateCode(null)).to.deep.equal({
|
||||
code: null,
|
||||
codeError: ERRORS.invalidCode,
|
||||
error: ERRORS.invalidCode
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on empty code', () => {
|
||||
expect(validateCode('')).to.deep.equal({
|
||||
code: '',
|
||||
codeError: ERRORS.invalidCode,
|
||||
error: ERRORS.invalidCode
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on non-hex code', () => {
|
||||
expect(validateCode('123hfg')).to.deep.equal({
|
||||
code: '123hfg',
|
||||
codeError: ERRORS.invalidCode,
|
||||
error: ERRORS.invalidCode
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateName', () => {
|
||||
it('validates names', () => {
|
||||
expect(validateName('Joe Bloggs')).to.deep.equal({
|
||||
name: 'Joe Bloggs',
|
||||
nameError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on null names', () => {
|
||||
expect(validateName(null)).to.deep.equal({
|
||||
name: null,
|
||||
nameError: ERRORS.invalidName,
|
||||
error: ERRORS.invalidName
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on short names', () => {
|
||||
expect(validateName(' 1 ')).to.deep.equal({
|
||||
name: ' 1 ',
|
||||
nameError: ERRORS.invalidName,
|
||||
error: ERRORS.invalidName
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validatePositiveNumber', () => {
|
||||
it('validates numbers', () => {
|
||||
expect(validatePositiveNumber(123)).to.deep.equal({
|
||||
number: 123,
|
||||
numberError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('validates strings', () => {
|
||||
expect(validatePositiveNumber('123')).to.deep.equal({
|
||||
number: '123',
|
||||
numberError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('validates bignumbers', () => {
|
||||
expect(validatePositiveNumber(new BigNumber(123))).to.deep.equal({
|
||||
number: new BigNumber(123),
|
||||
numberError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on invalid numbers', () => {
|
||||
expect(validatePositiveNumber(null)).to.deep.equal({
|
||||
number: null,
|
||||
numberError: ERRORS.invalidAmount,
|
||||
error: ERRORS.invalidAmount
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on negative numbers', () => {
|
||||
expect(validatePositiveNumber(-1)).to.deep.equal({
|
||||
number: -1,
|
||||
numberError: ERRORS.invalidAmount,
|
||||
error: ERRORS.invalidAmount
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateUint', () => {
|
||||
it('validates numbers', () => {
|
||||
expect(validateUint(123)).to.deep.equal({
|
||||
value: 123,
|
||||
valueError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('validates strings', () => {
|
||||
expect(validateUint('123')).to.deep.equal({
|
||||
value: '123',
|
||||
valueError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('validates bignumbers', () => {
|
||||
expect(validateUint(new BigNumber(123))).to.deep.equal({
|
||||
value: new BigNumber(123),
|
||||
valueError: null,
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on invalid numbers', () => {
|
||||
expect(validateUint(null)).to.deep.equal({
|
||||
value: null,
|
||||
valueError: ERRORS.invalidNumber,
|
||||
error: ERRORS.invalidNumber
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on negative numbers', () => {
|
||||
expect(validateUint(-1)).to.deep.equal({
|
||||
value: -1,
|
||||
valueError: ERRORS.negativeNumber,
|
||||
error: ERRORS.negativeNumber
|
||||
});
|
||||
});
|
||||
|
||||
it('sets error on decimal numbers', () => {
|
||||
expect(validateUint(3.1415927)).to.deep.equal({
|
||||
value: 3.1415927,
|
||||
valueError: ERRORS.decimalNumber,
|
||||
error: ERRORS.decimalNumber
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNullAddress', () => {
|
||||
it('verifies a prefixed null address', () => {
|
||||
expect(isNullAddress(`0x${NULL_ADDRESS}`)).to.be.true;
|
||||
});
|
||||
|
||||
it('verifies a non-prefixed null address', () => {
|
||||
expect(isNullAddress(NULL_ADDRESS)).to.be.true;
|
||||
});
|
||||
|
||||
it('sets false on a null value', () => {
|
||||
expect(isNullAddress(null)).to.be.false;
|
||||
});
|
||||
|
||||
it('sets false on a non-full length 00..00 value', () => {
|
||||
expect(isNullAddress(NULL_ADDRESS.slice(2))).to.be.false;
|
||||
});
|
||||
|
||||
it('sets false on a valid addess, non 00..00 value', () => {
|
||||
expect(isNullAddress('0x1234567890123456789012345678901234567890')).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,405 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 { intersection, range, uniq } from 'lodash';
|
||||
import store from 'store';
|
||||
|
||||
import Abi from '@parity/abi';
|
||||
import Contract from '@parity/api/contract';
|
||||
import { bytesToHex, toHex } from '@parity/api/util/format';
|
||||
|
||||
import { validateAddress } from '~/util/validation';
|
||||
import WalletAbi from '~/contracts/abi/wallet.json';
|
||||
import OldWalletAbi from '~/contracts/abi/old-wallet.json';
|
||||
|
||||
const LS_PENDING_CONTRACTS_KEY = '_parity::wallets::pendingContracts';
|
||||
|
||||
const _cachedWalletLookup = {};
|
||||
let _cachedAccounts = {};
|
||||
|
||||
const walletAbi = new Abi(WalletAbi);
|
||||
const oldWalletAbi = new Abi(OldWalletAbi);
|
||||
|
||||
const walletEvents = walletAbi.events.reduce((events, event) => {
|
||||
events[event.name] = event;
|
||||
return events;
|
||||
}, {});
|
||||
|
||||
const oldWalletEvents = oldWalletAbi.events.reduce((events, event) => {
|
||||
events[event.name] = event;
|
||||
return events;
|
||||
}, {});
|
||||
|
||||
const WalletSignatures = {
|
||||
OwnerChanged: toHex(walletEvents.OwnerChanged.signature),
|
||||
OwnerAdded: toHex(walletEvents.OwnerAdded.signature),
|
||||
OwnerRemoved: toHex(walletEvents.OwnerRemoved.signature),
|
||||
RequirementChanged: toHex(walletEvents.RequirementChanged.signature),
|
||||
Confirmation: toHex(walletEvents.Confirmation.signature),
|
||||
Revoke: toHex(walletEvents.Revoke.signature),
|
||||
Deposit: toHex(walletEvents.Deposit.signature),
|
||||
SingleTransact: toHex(walletEvents.SingleTransact.signature),
|
||||
MultiTransact: toHex(walletEvents.MultiTransact.signature),
|
||||
ConfirmationNeeded: toHex(walletEvents.ConfirmationNeeded.signature),
|
||||
|
||||
Old: {
|
||||
SingleTransact: toHex(oldWalletEvents.SingleTransact.signature),
|
||||
MultiTransact: toHex(oldWalletEvents.MultiTransact.signature)
|
||||
}
|
||||
};
|
||||
|
||||
export default class WalletsUtils {
|
||||
static getWalletSignatures () {
|
||||
return WalletSignatures;
|
||||
}
|
||||
|
||||
static getPendingContracts () {
|
||||
return store.get(LS_PENDING_CONTRACTS_KEY) || {};
|
||||
}
|
||||
|
||||
static setPendingContracts (contracts = {}) {
|
||||
return store.set(LS_PENDING_CONTRACTS_KEY, contracts);
|
||||
}
|
||||
|
||||
static removePendingContract (operationHash) {
|
||||
const nextContracts = WalletsUtils.getPendingContracts();
|
||||
|
||||
delete nextContracts[operationHash];
|
||||
WalletsUtils.setPendingContracts(nextContracts);
|
||||
}
|
||||
|
||||
static addPendingContract (address, operationHash, metadata) {
|
||||
const nextContracts = {
|
||||
...WalletsUtils.getPendingContracts(),
|
||||
[ operationHash ]: {
|
||||
address,
|
||||
metadata,
|
||||
operationHash
|
||||
}
|
||||
};
|
||||
|
||||
WalletsUtils.setPendingContracts(nextContracts);
|
||||
}
|
||||
|
||||
static cacheAccounts (accounts) {
|
||||
_cachedAccounts = accounts;
|
||||
}
|
||||
|
||||
static getCallArgs (api, options, values = []) {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
const walletAddress = options.from;
|
||||
|
||||
return WalletsUtils
|
||||
.fetchOwners(walletContract.at(walletAddress))
|
||||
.then((owners) => {
|
||||
const addresses = Object.keys(_cachedAccounts);
|
||||
const ownerAddress = intersection(addresses, owners).pop();
|
||||
|
||||
if (!ownerAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const account = _cachedAccounts[ownerAddress];
|
||||
const _options = { ...options };
|
||||
const { to, value = new BigNumber(0), data } = _options;
|
||||
|
||||
delete _options.data;
|
||||
|
||||
const nextValues = [ to, value, data ];
|
||||
const nextOptions = {
|
||||
..._options,
|
||||
from: ownerAddress,
|
||||
to: walletAddress,
|
||||
value: new BigNumber(0)
|
||||
};
|
||||
|
||||
const execFunc = walletContract.instance.execute;
|
||||
const callArgs = { func: execFunc, options: nextOptions, values: nextValues };
|
||||
|
||||
if (!account.wallet) {
|
||||
return callArgs;
|
||||
}
|
||||
|
||||
const nextData = walletContract.getCallData(execFunc, nextOptions, nextValues);
|
||||
|
||||
return WalletsUtils.getCallArgs(api, { ...nextOptions, data: nextData }, nextValues);
|
||||
});
|
||||
}
|
||||
|
||||
static getDeployArgs (contract, options, values) {
|
||||
const { api } = contract;
|
||||
const func = contract.constructors[0];
|
||||
|
||||
options.data = contract.getCallData(func, options, values);
|
||||
options.to = '0x';
|
||||
|
||||
return WalletsUtils
|
||||
.getCallArgs(api, options, values)
|
||||
.then((callArgs) => {
|
||||
if (!callArgs) {
|
||||
console.error('no call args', callArgs);
|
||||
throw new Error('you do not own this wallet');
|
||||
}
|
||||
|
||||
return callArgs;
|
||||
});
|
||||
}
|
||||
|
||||
static parseLogs (api, logs = []) {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
|
||||
return walletContract.parseEventLogs(logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given address could be
|
||||
* a Wallet. The result is cached in order not
|
||||
* to make unnecessary calls on non-wallet accounts
|
||||
*/
|
||||
static isWallet (api, address) {
|
||||
if (!address) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (!_cachedWalletLookup[address]) {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
|
||||
_cachedWalletLookup[address] = walletContract
|
||||
.at(address)
|
||||
.instance
|
||||
.m_numOwners
|
||||
.call()
|
||||
.then((result) => {
|
||||
if (!result || result.equals(0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.then((bool) => {
|
||||
_cachedWalletLookup[address] = Promise.resolve(bool);
|
||||
return bool;
|
||||
});
|
||||
}
|
||||
|
||||
return _cachedWalletLookup[address];
|
||||
}
|
||||
|
||||
static fetchRequire (walletContract) {
|
||||
return walletContract.instance.m_required.call();
|
||||
}
|
||||
|
||||
static fetchOwners (walletContract) {
|
||||
const walletInstance = walletContract.instance;
|
||||
|
||||
return walletInstance
|
||||
.m_numOwners.call()
|
||||
.then((mNumOwners) => {
|
||||
const promises = range(mNumOwners.toNumber())
|
||||
.map((idx) => walletInstance.getOwner.call({}, [ idx ]));
|
||||
|
||||
return Promise
|
||||
.all(promises)
|
||||
.then((owners) => {
|
||||
const uniqOwners = uniq(owners);
|
||||
|
||||
// If all owners are the zero account : must be Mist wallet contract
|
||||
if (uniqOwners.length === 1 && /^(0x)?0*$/.test(owners[0])) {
|
||||
return WalletsUtils.fetchMistOwners(walletContract, mNumOwners.toNumber());
|
||||
}
|
||||
|
||||
return owners;
|
||||
})
|
||||
.then((owners) => uniq(owners));
|
||||
});
|
||||
}
|
||||
|
||||
static fetchMistOwners (walletContract, mNumOwners) {
|
||||
const walletAddress = walletContract.address;
|
||||
|
||||
return WalletsUtils
|
||||
.getMistOwnersOffset(walletContract)
|
||||
.then((result) => {
|
||||
if (!result || result.offset === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const owners = [ result.address ];
|
||||
|
||||
if (mNumOwners === 1) {
|
||||
return owners;
|
||||
}
|
||||
|
||||
const initOffset = result.offset + 1;
|
||||
let promise = Promise.resolve();
|
||||
|
||||
range(initOffset, initOffset + mNumOwners - 1).forEach((offset) => {
|
||||
promise = promise
|
||||
.then(() => {
|
||||
return walletContract.api.eth.getStorageAt(walletAddress, offset);
|
||||
})
|
||||
.then((result) => {
|
||||
const resultAddress = '0x' + (result || '').slice(-40);
|
||||
const { address } = validateAddress(resultAddress);
|
||||
|
||||
owners.push(address);
|
||||
});
|
||||
});
|
||||
|
||||
return promise.then(() => owners);
|
||||
});
|
||||
}
|
||||
|
||||
static getMistOwnersOffset (walletContract, offset = 3) {
|
||||
return walletContract.api.eth
|
||||
.getStorageAt(walletContract.address, offset)
|
||||
.then((result) => {
|
||||
if (result && !/^(0x)?0*$/.test(result)) {
|
||||
const resultAddress = '0x' + result.slice(-40);
|
||||
const { address, addressError } = validateAddress(resultAddress);
|
||||
|
||||
if (!addressError) {
|
||||
return { offset, address };
|
||||
}
|
||||
}
|
||||
|
||||
if (offset >= 100) {
|
||||
return { offset: -1 };
|
||||
}
|
||||
|
||||
return WalletsUtils.getMistOwnersOffset(walletContract, offset + 1);
|
||||
});
|
||||
}
|
||||
|
||||
static fetchDailylimit (walletContract) {
|
||||
const walletInstance = walletContract.instance;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
walletInstance.m_dailyLimit.call(),
|
||||
walletInstance.m_spentToday.call(),
|
||||
walletInstance.m_lastDay.call()
|
||||
])
|
||||
.then(([ limit, spent, last ]) => ({
|
||||
limit, spent, last
|
||||
}));
|
||||
}
|
||||
|
||||
static fetchTransactions (walletContract) {
|
||||
const { api } = walletContract;
|
||||
const pendingContracts = WalletsUtils.getPendingContracts();
|
||||
|
||||
return walletContract
|
||||
.getAllLogs({
|
||||
topics: [ [
|
||||
WalletSignatures.SingleTransact,
|
||||
WalletSignatures.MultiTransact,
|
||||
WalletSignatures.Deposit,
|
||||
WalletSignatures.Old.SingleTransact,
|
||||
WalletSignatures.Old.MultiTransact
|
||||
] ]
|
||||
})
|
||||
.then((logs) => {
|
||||
return logs.sort((logA, logB) => {
|
||||
const comp = logB.blockNumber.comparedTo(logA.blockNumber);
|
||||
|
||||
if (comp !== 0) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
return logB.transactionIndex.comparedTo(logA.transactionIndex);
|
||||
});
|
||||
})
|
||||
.then((logs) => {
|
||||
const transactions = logs.map((log) => {
|
||||
const signature = toHex(log.topics[0]);
|
||||
|
||||
const value = log.params.value.value;
|
||||
const from = signature === WalletSignatures.Deposit
|
||||
? log.params['_from'].value
|
||||
: walletContract.address;
|
||||
|
||||
const to = signature === WalletSignatures.Deposit
|
||||
? walletContract.address
|
||||
: log.params.to.value;
|
||||
|
||||
const transaction = {
|
||||
transactionHash: log.transactionHash,
|
||||
blockNumber: log.blockNumber,
|
||||
from, to, value
|
||||
};
|
||||
|
||||
if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
|
||||
transaction.creates = log.params.created.value;
|
||||
delete transaction.to;
|
||||
}
|
||||
|
||||
if (log.params.operation) {
|
||||
const operation = bytesToHex(log.params.operation.value);
|
||||
|
||||
// Add the pending contract to the contracts
|
||||
if (pendingContracts[operation]) {
|
||||
const { metadata } = pendingContracts[operation];
|
||||
const contractName = metadata.name;
|
||||
|
||||
metadata.blockNumber = log.blockNumber;
|
||||
|
||||
// The contract creation might not be in the same log,
|
||||
// but must be in the same transaction (eg. Contract creation
|
||||
// from Wallet within a Wallet)
|
||||
api.eth
|
||||
.getTransactionReceipt(log.transactionHash)
|
||||
.then((transactionReceipt) => {
|
||||
const transactionLogs = WalletsUtils.parseLogs(api, transactionReceipt.logs);
|
||||
const creationLog = transactionLogs.find((log) => {
|
||||
return log.params.created && !/^(0x)?0*$/.test(log.params.created.value);
|
||||
});
|
||||
|
||||
if (!creationLog) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contractAddress = creationLog.params.created.value;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
api.parity.setAccountName(contractAddress, contractName),
|
||||
api.parity.setAccountMeta(contractAddress, metadata)
|
||||
])
|
||||
.then(() => {
|
||||
WalletsUtils.removePendingContract(operation);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('adding wallet contract', error);
|
||||
});
|
||||
}
|
||||
|
||||
transaction.operation = operation;
|
||||
}
|
||||
|
||||
if (log.params.data) {
|
||||
transaction.data = log.params.data.value;
|
||||
}
|
||||
|
||||
return transaction;
|
||||
});
|
||||
|
||||
return transactions;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 parity from '~/jsonrpc/interfaces/parity';
|
||||
import signer from '~/jsonrpc/interfaces/signer';
|
||||
import trace from '~/jsonrpc/interfaces/trace';
|
||||
|
||||
export default function web3extensions (web3) {
|
||||
const { Method } = web3._extend;
|
||||
|
||||
// TODO [ToDr] Consider output/input formatters.
|
||||
const methods = (object, name) => {
|
||||
return Object.keys(object).map(method => {
|
||||
return new Method({
|
||||
name: method,
|
||||
call: `${name}_{method}`,
|
||||
params: object[method].params.length
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return [{
|
||||
property: 'parity',
|
||||
methods: methods(parity, 'parity'),
|
||||
properties: []
|
||||
}, {
|
||||
property: 'signer',
|
||||
methods: methods(signer, 'signer'),
|
||||
properties: []
|
||||
}, {
|
||||
property: 'trace',
|
||||
methods: methods(trace, 'trace'),
|
||||
properties: []
|
||||
}];
|
||||
}
|
||||
Reference in New Issue
Block a user