Minimise transactions progress (#4942)

* Watch the requests and display them throughout the app

* Linting

* Showing Requests

* Fully working Transaction Requests Display

* Add FormattedMessage to Requests

* Clean-up the Transfer dialog

* Update Validations

* Cleanup Create Wallet

* Clean Deploy Contract Dialog

* Cleanup Contract Execution

* Fix Requests

* Cleanup Wallet Settings

* Don't show stepper in Portal if less than 2 steps

* WIP local storage requests

* Caching requests and saving contract deployments

* Add Historic prop to Requests MethodDecoding

* Fix tests

* Add Contract address to MethodDecoding

* PR Grumbles - Part I

* PR Grumbles - Part II

* Use API Subscription methods

* Linting

* Move SavedRequests and add tests

* Added tests for Requests Actions

* Fixing tests

* PR Grumbles + Playground fix

* Revert Playground changes

* PR Grumbles

* Better showEth in MethodDecoding
This commit is contained in:
Nicolas Gotchac
2017-03-28 14:34:31 +02:00
committed by Jaco Greeff
parent e28c477075
commit a99721004b
40 changed files with 1382 additions and 1216 deletions

View File

@@ -16,6 +16,25 @@
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);
};
@@ -73,100 +92,6 @@ export function postTransaction (_func, _options, _values = []) {
});
}
export function deploy (contract, _options, values, metadata = {}, statecb = () => {}, skipGasEstimate = false) {
const options = { ..._options };
const { api } = contract;
const address = options.from;
return WalletsUtils
.isWallet(api, address)
.then((isWallet) => {
if (!isWallet) {
return contract.deploy(options, values, statecb, skipGasEstimate);
}
let gasEstPromise;
if (skipGasEstimate) {
gasEstPromise = Promise.resolve(null);
} else {
statecb(null, { state: 'estimateGas' });
gasEstPromise = deployEstimateGas(contract, options, values)
.then(([gasEst, gas]) => gas);
}
return gasEstPromise
.then((gas) => {
if (gas) {
options.gas = gas.toFixed(0);
}
statecb(null, { state: 'postTransaction', gas: options.gas });
return WalletsUtils.getDeployArgs(contract, options, values);
})
.then((callArgs) => {
const { func, options, values } = callArgs;
return func._postTransaction(options, values)
.then((requestId) => {
statecb(null, { state: 'checkRequest', requestId });
return contract._pollCheckRequest(requestId);
})
.then((txhash) => {
statecb(null, { state: 'getTransactionReceipt', txhash });
return contract._pollTransactionReceipt(txhash, options.gas);
})
.then((receipt) => {
if (receipt.gasUsed.eq(options.gas)) {
throw new Error(`Contract not deployed, gasUsed == ${options.gas.toFixed(0)}`);
}
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) {
throw new Error('Something went wrong in the Wallet Contract (no logs have been emitted)...');
}
// 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);
statecb(null, { state: 'confirmationNeeded' });
return;
}
// Set the contract address in the receip
receipt.contractAddress = transactionLog.params.created.value;
const contractAddress = receipt.contractAddress;
statecb(null, { state: 'hasReceipt', receipt });
contract._receipt = receipt;
contract._address = contractAddress;
statecb(null, { state: 'getCode' });
return api.eth.getCode(contractAddress)
.then((code) => {
if (code === '0x') {
throw new Error('Contract not deployed, getCode returned 0x');
}
statecb(null, { state: 'completed' });
return contractAddress;
});
});
});
});
}
export function deployEstimateGas (contract, _options, values) {
const options = { ..._options };
const { api } = contract;
@@ -192,6 +117,86 @@ export function deployEstimateGas (contract, _options, values) {
});
}
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,

View File

@@ -25,6 +25,7 @@ import { NULL_ADDRESS } from './constants';
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',
@@ -48,6 +49,7 @@ export function validateAbi (abi) {
abiError = ERRORS.invalidAbi;
return {
error: abiError,
abi,
abiError,
abiParsed
@@ -66,6 +68,7 @@ export function validateAbi (abi) {
abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`;
return {
error: abiError,
abi,
abiError,
abiParsed
@@ -78,6 +81,7 @@ export function validateAbi (abi) {
}
return {
error: abiError,
abi,
abiError,
abiParsed
@@ -123,6 +127,7 @@ export function validateAddress (address) {
}
return {
error: addressError,
address,
addressError
};
@@ -138,6 +143,7 @@ export function validateCode (code) {
}
return {
error: codeError,
code,
codeError
};
@@ -149,6 +155,7 @@ export function validateName (name) {
: null;
return {
error: nameError,
name,
nameError
};
@@ -168,6 +175,27 @@ export function validatePositiveNumber (number) {
}
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
};
@@ -189,6 +217,7 @@ export function validateUint (value) {
}
return {
error: valueError,
value,
valueError
};

View File

@@ -32,7 +32,8 @@ describe('util/validation', () => {
name: 'test',
inputs: [],
outputs: []
}]
}],
error: null
});
});
@@ -47,7 +48,8 @@ describe('util/validation', () => {
name: 'test',
inputs: [],
outputs: []
}]
}],
error: null
});
});
@@ -57,7 +59,8 @@ describe('util/validation', () => {
expect(validateAbi(abi)).to.deep.equal({
abi,
abiError: ERRORS.invalidAbi,
abiParsed: null
abiParsed: null,
error: ERRORS.invalidAbi
});
});
@@ -67,7 +70,8 @@ describe('util/validation', () => {
expect(validateAbi(abi)).to.deep.equal({
abi,
abiError: ERRORS.invalidAbi,
abiParsed: {}
abiParsed: {},
error: ERRORS.invalidAbi
});
});
@@ -77,7 +81,8 @@ describe('util/validation', () => {
expect(validateAbi(abi)).to.deep.equal({
abi,
abiError: `${ERRORS.invalidAbi} (#0: event)`,
abiParsed: [{ type: 'event' }]
abiParsed: [{ type: 'event' }],
error: `${ERRORS.invalidAbi} (#0: event)`
});
});
@@ -87,7 +92,8 @@ describe('util/validation', () => {
expect(validateAbi(abi)).to.deep.equal({
abi,
abiError: `${ERRORS.invalidAbi} (#0: function)`,
abiParsed: [{ type: 'function' }]
abiParsed: [{ type: 'function' }],
error: `${ERRORS.invalidAbi} (#0: function)`
});
});
@@ -97,7 +103,8 @@ describe('util/validation', () => {
expect(validateAbi(abi)).to.deep.equal({
abi,
abiError: `${ERRORS.invalidAbi} (#0: somethingElse)`,
abiParsed: [{ type: 'somethingElse' }]
abiParsed: [{ type: 'somethingElse' }],
error: `${ERRORS.invalidAbi} (#0: somethingElse)`
});
});
});
@@ -108,7 +115,8 @@ describe('util/validation', () => {
expect(validateAddress(address)).to.deep.equal({
address,
addressError: null
addressError: null,
error: null
});
});
@@ -117,14 +125,16 @@ describe('util/validation', () => {
expect(validateAddress(address.toLowerCase())).to.deep.equal({
address,
addressError: null
addressError: null,
error: null
});
});
it('sets error on null addresses', () => {
expect(validateAddress(null)).to.deep.equal({
address: null,
addressError: ERRORS.invalidAddress
addressError: ERRORS.invalidAddress,
error: ERRORS.invalidAddress
});
});
@@ -133,7 +143,8 @@ describe('util/validation', () => {
expect(validateAddress(address)).to.deep.equal({
address,
addressError: ERRORS.invalidAddress
addressError: ERRORS.invalidAddress,
error: ERRORS.invalidAddress
});
});
});
@@ -142,35 +153,40 @@ describe('util/validation', () => {
it('validates hex code', () => {
expect(validateCode('0x123abc')).to.deep.equal({
code: '0x123abc',
codeError: null
codeError: null,
error: null
});
});
it('validates hex code (non-prefix)', () => {
expect(validateCode('123abc')).to.deep.equal({
code: '123abc',
codeError: null
codeError: null,
error: null
});
});
it('sets error on invalid code', () => {
expect(validateCode(null)).to.deep.equal({
code: null,
codeError: ERRORS.invalidCode
codeError: ERRORS.invalidCode,
error: ERRORS.invalidCode
});
});
it('sets error on empty code', () => {
expect(validateCode('')).to.deep.equal({
code: '',
codeError: ERRORS.invalidCode
codeError: ERRORS.invalidCode,
error: ERRORS.invalidCode
});
});
it('sets error on non-hex code', () => {
expect(validateCode('123hfg')).to.deep.equal({
code: '123hfg',
codeError: ERRORS.invalidCode
codeError: ERRORS.invalidCode,
error: ERRORS.invalidCode
});
});
});
@@ -179,21 +195,24 @@ describe('util/validation', () => {
it('validates names', () => {
expect(validateName('Joe Bloggs')).to.deep.equal({
name: 'Joe Bloggs',
nameError: null
nameError: null,
error: null
});
});
it('sets error on null names', () => {
expect(validateName(null)).to.deep.equal({
name: null,
nameError: ERRORS.invalidName
nameError: ERRORS.invalidName,
error: ERRORS.invalidName
});
});
it('sets error on short names', () => {
expect(validateName(' 1 ')).to.deep.equal({
name: ' 1 ',
nameError: ERRORS.invalidName
nameError: ERRORS.invalidName,
error: ERRORS.invalidName
});
});
});
@@ -202,35 +221,40 @@ describe('util/validation', () => {
it('validates numbers', () => {
expect(validatePositiveNumber(123)).to.deep.equal({
number: 123,
numberError: null
numberError: null,
error: null
});
});
it('validates strings', () => {
expect(validatePositiveNumber('123')).to.deep.equal({
number: '123',
numberError: null
numberError: null,
error: null
});
});
it('validates bignumbers', () => {
expect(validatePositiveNumber(new BigNumber(123))).to.deep.equal({
number: new BigNumber(123),
numberError: null
numberError: null,
error: null
});
});
it('sets error on invalid numbers', () => {
expect(validatePositiveNumber(null)).to.deep.equal({
number: null,
numberError: ERRORS.invalidAmount
numberError: ERRORS.invalidAmount,
error: ERRORS.invalidAmount
});
});
it('sets error on negative numbers', () => {
expect(validatePositiveNumber(-1)).to.deep.equal({
number: -1,
numberError: ERRORS.invalidAmount
numberError: ERRORS.invalidAmount,
error: ERRORS.invalidAmount
});
});
});
@@ -239,42 +263,48 @@ describe('util/validation', () => {
it('validates numbers', () => {
expect(validateUint(123)).to.deep.equal({
value: 123,
valueError: null
valueError: null,
error: null
});
});
it('validates strings', () => {
expect(validateUint('123')).to.deep.equal({
value: '123',
valueError: null
valueError: null,
error: null
});
});
it('validates bignumbers', () => {
expect(validateUint(new BigNumber(123))).to.deep.equal({
value: new BigNumber(123),
valueError: null
valueError: null,
error: null
});
});
it('sets error on invalid numbers', () => {
expect(validateUint(null)).to.deep.equal({
value: null,
valueError: ERRORS.invalidNumber
valueError: ERRORS.invalidNumber,
error: ERRORS.invalidNumber
});
});
it('sets error on negative numbers', () => {
expect(validateUint(-1)).to.deep.equal({
value: -1,
valueError: ERRORS.negativeNumber
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
valueError: ERRORS.decimalNumber,
error: ERRORS.decimalNumber
});
});
});