aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacob Evans <jacob@dekz.net>2018-10-09 15:26:13 +0800
committerJacob Evans <jacob@dekz.net>2018-10-09 16:01:36 +0800
commit9e8031d5e3cf94cabe07685be510397367e90413 (patch)
tree548a3918ed9eb5325db3973d76924907b142aae0
parente1236a484623e9d2caab823c476175cb255ae816 (diff)
downloaddexon-0x-contracts-9e8031d5e3cf94cabe07685be510397367e90413.tar.gz
dexon-0x-contracts-9e8031d5e3cf94cabe07685be510397367e90413.tar.zst
dexon-0x-contracts-9e8031d5e3cf94cabe07685be510397367e90413.zip
Throw and handle errors from Providers.
In web3 wrapper when a response contains an error field we throw this rather than return response.result which is often undefined. In Signature Utils we handle the error thrown when a user rejects the signing dialogue to prevent double signing. Exposed the ZeroExTransaction JSON schema. In Website only use the MetamaskSubprovider if we can detect the provider is Metamask
-rw-r--r--packages/0x.js/CHANGELOG.json3
-rw-r--r--packages/0x.js/src/index.ts1
-rw-r--r--packages/contract-wrappers/src/utils/transaction_encoder.ts2
-rw-r--r--packages/contracts/test/utils/transaction_factory.ts2
-rw-r--r--packages/ethereum-types/CHANGELOG.json9
-rw-r--r--packages/ethereum-types/src/index.ts6
-rw-r--r--packages/json-schemas/schemas/eip712_typed_data.ts2
-rw-r--r--packages/json-schemas/schemas/zero_ex_transaction_schema.ts10
-rw-r--r--packages/json-schemas/src/schemas.ts6
-rw-r--r--packages/order-utils/src/eip712_utils.ts8
-rw-r--r--packages/order-utils/src/order_hash.ts2
-rw-r--r--packages/order-utils/src/signature_utils.ts26
-rw-r--r--packages/order-utils/test/eip712_utils_test.ts2
-rw-r--r--packages/order-utils/test/signature_utils_test.ts149
-rw-r--r--packages/subproviders/src/index.ts8
-rw-r--r--packages/subproviders/src/subproviders/private_key_wallet.ts2
-rw-r--r--packages/utils/src/sign_typed_data_utils.ts6
-rw-r--r--packages/utils/test/sign_typed_data_utils_test.ts4
-rw-r--r--packages/web3-wrapper/CHANGELOG.json14
-rw-r--r--packages/web3-wrapper/src/web3_wrapper.ts5
-rw-r--r--packages/web3-wrapper/test/web3_wrapper_test.ts15
-rw-r--r--packages/website/ts/blockchain.ts12
-rw-r--r--yarn.lock64
23 files changed, 208 insertions, 150 deletions
diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json
index 3a184382c..6dfcc3d33 100644
--- a/packages/0x.js/CHANGELOG.json
+++ b/packages/0x.js/CHANGELOG.json
@@ -7,7 +7,8 @@
"pr": 1102
},
{
- "note": "Added `MetamaskSubprovider` to handle inconsistencies with signing.",
+ "note":
+ "Added `MetamaskSubprovider` to handle inconsistencies in Metamask's signing JSON RPC endpoints.",
"pr": 1102
},
{
diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts
index 228bcecdb..6eb1fd8ee 100644
--- a/packages/0x.js/src/index.ts
+++ b/packages/0x.js/src/index.ts
@@ -90,6 +90,7 @@ export {
JSONRPCRequestPayload,
JSONRPCResponsePayload,
JSONRPCErrorCallback,
+ JSONRPCResponseError,
LogEntry,
DecodedLogArgs,
LogEntryEvent,
diff --git a/packages/contract-wrappers/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts
index d9735778e..33086944b 100644
--- a/packages/contract-wrappers/src/utils/transaction_encoder.ts
+++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts
@@ -33,7 +33,7 @@ export class TransactionEncoder {
data,
};
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, exchangeAddress);
- const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
+ const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
const messageHex = `0x${eip712MessageBuffer.toString('hex')}`;
return messageHex;
}
diff --git a/packages/contracts/test/utils/transaction_factory.ts b/packages/contracts/test/utils/transaction_factory.ts
index 6e4cc4b2f..9ed4c5a31 100644
--- a/packages/contracts/test/utils/transaction_factory.ts
+++ b/packages/contracts/test/utils/transaction_factory.ts
@@ -25,7 +25,7 @@ export class TransactionFactory {
};
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, this._exchangeAddress);
- const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
+ const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType);
const signedTx = {
exchangeAddress: this._exchangeAddress,
diff --git a/packages/ethereum-types/CHANGELOG.json b/packages/ethereum-types/CHANGELOG.json
index e955f4d04..60fb8c806 100644
--- a/packages/ethereum-types/CHANGELOG.json
+++ b/packages/ethereum-types/CHANGELOG.json
@@ -1,5 +1,14 @@
[
{
+ "version": "1.1.0",
+ "changes": [
+ {
+ "note": "Add `JSONRPCResponseError` and error field on `JSONRPCResponsePayload`.",
+ "pr": 1102
+ }
+ ]
+ },
+ {
"timestamp": 1538693146,
"version": "1.0.11",
"changes": [
diff --git a/packages/ethereum-types/src/index.ts b/packages/ethereum-types/src/index.ts
index 7e8b9de3e..ddd472010 100644
--- a/packages/ethereum-types/src/index.ts
+++ b/packages/ethereum-types/src/index.ts
@@ -113,10 +113,16 @@ export interface JSONRPCRequestPayload {
jsonrpc: string;
}
+export interface JSONRPCResponseError {
+ message: string;
+ code: number;
+}
+
export interface JSONRPCResponsePayload {
result: any;
id: number;
jsonrpc: string;
+ error?: JSONRPCResponseError;
}
export interface AbstractBlock {
diff --git a/packages/json-schemas/schemas/eip712_typed_data.ts b/packages/json-schemas/schemas/eip712_typed_data.ts
index 371ff8a78..31ad74610 100644
--- a/packages/json-schemas/schemas/eip712_typed_data.ts
+++ b/packages/json-schemas/schemas/eip712_typed_data.ts
@@ -1,4 +1,4 @@
-export const eip712TypedData = {
+export const eip712TypedDataSchema = {
id: '/eip712TypedData',
type: 'object',
properties: {
diff --git a/packages/json-schemas/schemas/zero_ex_transaction_schema.ts b/packages/json-schemas/schemas/zero_ex_transaction_schema.ts
new file mode 100644
index 000000000..7f729b724
--- /dev/null
+++ b/packages/json-schemas/schemas/zero_ex_transaction_schema.ts
@@ -0,0 +1,10 @@
+export const zeroExTransactionSchema = {
+ id: '/zeroExTransactionSchema',
+ properties: {
+ data: { $ref: '/hexSchema' },
+ signerAddress: { $ref: '/addressSchema' },
+ salt: { $ref: '/numberSchema' },
+ },
+ required: ['data', 'salt', 'signerAddress'],
+ type: 'object',
+};
diff --git a/packages/json-schemas/src/schemas.ts b/packages/json-schemas/src/schemas.ts
index 50dc8d091..4eb96092d 100644
--- a/packages/json-schemas/src/schemas.ts
+++ b/packages/json-schemas/src/schemas.ts
@@ -2,7 +2,7 @@ import { addressSchema, hexSchema, numberSchema } from '../schemas/basic_type_sc
import { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema';
import { callDataSchema } from '../schemas/call_data_schema';
import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema';
-import { eip712TypedData } from '../schemas/eip712_typed_data';
+import { eip712TypedDataSchema } from '../schemas/eip712_typed_data';
import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema';
import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema';
import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema';
@@ -32,6 +32,7 @@ import { relayerApiOrdersSchema } from '../schemas/relayer_api_orders_schema';
import { signedOrdersSchema } from '../schemas/signed_orders_schema';
import { tokenSchema } from '../schemas/token_schema';
import { jsNumber, txDataSchema } from '../schemas/tx_data_schema';
+import { zeroExTransactionSchema } from '../schemas/zero_ex_transaction_schema';
export const schemas = {
numberSchema,
@@ -40,7 +41,7 @@ export const schemas = {
hexSchema,
ecSignatureParameterSchema,
ecSignatureSchema,
- eip712TypedData,
+ eip712TypedDataSchema,
indexFilterValuesSchema,
orderCancellationRequestsSchema,
orderFillOrKillRequestsSchema,
@@ -70,4 +71,5 @@ export const schemas = {
relayerApiOrdersChannelUpdateSchema,
relayerApiOrdersResponseSchema,
relayerApiAssetDataPairsSchema,
+ zeroExTransactionSchema,
};
diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts
index 43b421de7..56f736500 100644
--- a/packages/order-utils/src/eip712_utils.ts
+++ b/packages/order-utils/src/eip712_utils.ts
@@ -1,3 +1,5 @@
+import { assert } from '@0xproject/assert';
+import { schemas } from '@0xproject/json-schemas';
import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0xproject/types';
import * as _ from 'lodash';
@@ -18,6 +20,8 @@ export const eip712Utils = {
message: EIP712Object,
exchangeAddress: string,
): EIP712TypedData => {
+ assert.isETHAddressHex('exchangeAddress', exchangeAddress);
+ assert.isString('primaryType', primaryType);
const typedData = {
types: {
EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters,
@@ -31,6 +35,7 @@ export const eip712Utils = {
message,
primaryType,
};
+ assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
return typedData;
},
/**
@@ -39,6 +44,7 @@ export const eip712Utils = {
* @return A typed data object
*/
createOrderTypedData: (order: Order): EIP712TypedData => {
+ assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
const normalizedOrder = _.mapValues(order, value => {
return !_.isString(value) ? value.toString() : value;
});
@@ -61,6 +67,8 @@ export const eip712Utils = {
zeroExTransaction: ZeroExTransaction,
exchangeAddress: string,
): EIP712TypedData => {
+ assert.isETHAddressHex('exchangeAddress', exchangeAddress);
+ assert.doesConformToSchema('zeroExTransaction', zeroExTransaction, schemas.zeroExTransactionSchema);
const normalizedTransaction = _.mapValues(zeroExTransaction, value => {
return !_.isString(value) ? value.toString() : value;
});
diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts
index a6dd6688c..b523a3523 100644
--- a/packages/order-utils/src/order_hash.ts
+++ b/packages/order-utils/src/order_hash.ts
@@ -52,7 +52,7 @@ export const orderHashUtils = {
*/
getOrderHashBuffer(order: SignedOrder | Order): Buffer {
const typedData = eip712Utils.createOrderTypedData(order);
- const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData);
+ const orderHashBuff = signTypedDataUtils.generateTypedDataHash(typedData);
return orderHashBuff;
},
};
diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts
index e518b29a0..2605ccd32 100644
--- a/packages/order-utils/src/signature_utils.ts
+++ b/packages/order-utils/src/signature_utils.ts
@@ -194,7 +194,7 @@ export const signatureUtils = {
}
},
/**
- * Signs an order and returns its elliptic curve signature and signature type. First `eth_signTypedData` is requested
+ * Signs an order and returns a SignedOrder. First `eth_signTypedData` is requested
* then a fallback to `eth_sign` if not available on the supplied provider.
* @param order The Order to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
@@ -202,11 +202,18 @@ export const signatureUtils = {
* @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
*/
async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
+ assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
try {
- const signedOrder = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
+ const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
return signedOrder;
} catch (err) {
- // Fallback to using EthSign when ethSignTypedData is not supported
+ // HACK: We are unable to handle specific errors thrown since provider is not an object
+ // under our control. It could be Metamask Web3, Ethers, or any general RPC provider.
+ // We check for a user denying the signature request in a way that supports Metamask and
+ // Coinbase Wallet
+ if (err.message.includes('User denied message signature')) {
+ throw err;
+ }
const orderHash = orderHashUtils.getOrderHashHex(order);
const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress);
const signedOrder = {
@@ -217,7 +224,7 @@ export const signatureUtils = {
}
},
/**
- * Signs an order using `eth_signTypedData` and returns its elliptic curve signature and signature type.
+ * Signs an order using `eth_signTypedData` and returns a SignedOrder.
* @param order The Order to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the supplied Provider.
@@ -226,6 +233,7 @@ export const signatureUtils = {
async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
assert.isWeb3Provider('provider', provider);
assert.isETHAddressHex('signerAddress', signerAddress);
+ assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
const web3Wrapper = new Web3Wrapper(provider);
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
const normalizedSignerAddress = signerAddress.toLowerCase();
@@ -247,14 +255,16 @@ export const signatureUtils = {
} catch (err) {
// Detect if Metamask to transition users to the MetamaskSubprovider
if ((provider as any).isMetaMask) {
- throw new Error(`Unsupported Provider, please use MetamaskSubprovider: ${err.message}`);
+ throw new Error(
+ `MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`,
+ );
} else {
throw err;
}
}
},
/**
- * Signs a hash and returns its elliptic curve signature and signature type.
+ * Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type.
* @param msgHash Hex encoded message to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the supplied Provider.
@@ -303,7 +313,9 @@ export const signatureUtils = {
}
// Detect if Metamask to transition users to the MetamaskSubprovider
if ((provider as any).isMetaMask) {
- throw new Error('Unsupported Provider, please use MetamaskSubprovider.');
+ throw new Error(
+ `MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`,
+ );
} else {
throw new Error(OrderError.InvalidSignature);
}
diff --git a/packages/order-utils/test/eip712_utils_test.ts b/packages/order-utils/test/eip712_utils_test.ts
index dc76595db..d65cabe9c 100644
--- a/packages/order-utils/test/eip712_utils_test.ts
+++ b/packages/order-utils/test/eip712_utils_test.ts
@@ -33,7 +33,7 @@ describe('EIP712 Utils', () => {
{
salt: new BigNumber('0'),
data: constants.NULL_BYTES,
- signerAddress: constants.NULL_BYTES,
+ signerAddress: constants.NULL_ADDRESS,
},
constants.NULL_ADDRESS,
);
diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts
index 7f6987f6a..f2d6790fb 100644
--- a/packages/order-utils/test/signature_utils_test.ts
+++ b/packages/order-utils/test/signature_utils_test.ts
@@ -17,6 +17,28 @@ chaiSetup.configure();
const expect = chai.expect;
describe('Signature utils', () => {
+ let makerAddress: string;
+ const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
+ let order: Order;
+ before(async () => {
+ const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
+ makerAddress = availableAddreses[0];
+ order = {
+ makerAddress,
+ takerAddress: constants.NULL_ADDRESS,
+ senderAddress: constants.NULL_ADDRESS,
+ feeRecipientAddress: constants.NULL_ADDRESS,
+ makerAssetData: constants.NULL_ADDRESS,
+ takerAssetData: constants.NULL_ADDRESS,
+ exchangeAddress: fakeExchangeContractAddress,
+ salt: new BigNumber(0),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ makerAssetAmount: new BigNumber(0),
+ takerAssetAmount: new BigNumber(0),
+ expirationTimeSeconds: new BigNumber(0),
+ };
+ });
describe('#isValidSignatureAsync', () => {
let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const ethSignSignature =
@@ -117,54 +139,54 @@ describe('Signature utils', () => {
});
});
describe('#ecSignOrderAsync', () => {
- let makerAddress: string;
- const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
- let order: Order;
- before(async () => {
- const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
- makerAddress = availableAddreses[0];
- order = {
- makerAddress,
- takerAddress: constants.NULL_ADDRESS,
- senderAddress: constants.NULL_ADDRESS,
- feeRecipientAddress: constants.NULL_ADDRESS,
- makerAssetData: constants.NULL_ADDRESS,
- takerAssetData: constants.NULL_ADDRESS,
- exchangeAddress: fakeExchangeContractAddress,
- salt: new BigNumber(0),
- makerFee: new BigNumber(0),
- takerFee: new BigNumber(0),
- makerAssetAmount: new BigNumber(0),
- takerAssetAmount: new BigNumber(0),
- expirationTimeSeconds: new BigNumber(0),
+ it('should default to eth_sign if eth_signTypedData is unavailable', async () => {
+ const expectedSignature =
+ '0x1c3582f06356a1314dbf1c0e534c4d8e92e59b056ee607a7ff5a825f5f2cc5e6151c5cc7fdd420f5608e4d5bef108e42ad90c7a4b408caef32e24374cf387b0d7603';
+
+ const fakeProvider = {
+ async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
+ if (payload.method === 'eth_signTypedData') {
+ callback(new Error('Internal RPC Error'));
+ } else if (payload.method === 'eth_sign') {
+ const [address, message] = payload.params;
+ const signature = await web3Wrapper.signMessageAsync(address, message);
+ callback(null, {
+ id: 42,
+ jsonrpc: '2.0',
+ result: signature,
+ });
+ } else {
+ callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
+ }
+ },
};
+ const signedOrder = await signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress);
+ expect(signedOrder.signature).to.equal(expectedSignature);
});
- it('should result in the same signature as signing order hash without prefix', async () => {
- const orderHashHex = orderHashUtils.getOrderHashHex(order);
- const sig = ethUtil.ecsign(
- ethUtil.toBuffer(orderHashHex),
- Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'),
- );
- const signatureBuffer = Buffer.concat([
- ethUtil.toBuffer(sig.v),
- ethUtil.toBuffer(sig.r),
- ethUtil.toBuffer(sig.s),
- ethUtil.toBuffer(SignatureType.EIP712),
- ]);
- const signatureHex = `0x${signatureBuffer.toString('hex')}`;
- const signedOrder = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress);
- const isValidSignature = await signatureUtils.isValidSignatureAsync(
- provider,
- orderHashHex,
- signedOrder.signature,
- makerAddress,
+ it('should throw if the user denies the signing request', async () => {
+ const fakeProvider = {
+ async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
+ if (payload.method === 'eth_signTypedData') {
+ callback(new Error('User denied message signature'));
+ } else if (payload.method === 'eth_sign') {
+ const [address, message] = payload.params;
+ const signature = await web3Wrapper.signMessageAsync(address, message);
+ callback(null, {
+ id: 42,
+ jsonrpc: '2.0',
+ result: signature,
+ });
+ } else {
+ callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
+ }
+ },
+ };
+ expect(signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress)).to.to.be.rejectedWith(
+ 'User denied message signature',
);
- expect(signatureHex).to.eq(signedOrder.signature);
- expect(isValidSignature).to.eq(true);
});
});
describe('#ecSignHashAsync', () => {
- let makerAddress: string;
before(async () => {
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
makerAddress = availableAddreses[0];
@@ -239,27 +261,30 @@ describe('Signature utils', () => {
});
});
describe('#ecSignTypedDataOrderAsync', () => {
- let makerAddress: string;
- const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
- let order: Order;
- before(async () => {
- const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
- makerAddress = availableAddreses[0];
- order = {
+ it('should result in the same signature as signing the order hash without an ethereum message prefix', async () => {
+ // Note: Since order hash is an EIP712 hash the result of a valid EIP712 signature
+ // of order hash is the same as signing the order without the Ethereum Message prefix.
+ const orderHashHex = orderHashUtils.getOrderHashHex(order);
+ const sig = ethUtil.ecsign(
+ ethUtil.toBuffer(orderHashHex),
+ Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'),
+ );
+ const signatureBuffer = Buffer.concat([
+ ethUtil.toBuffer(sig.v),
+ ethUtil.toBuffer(sig.r),
+ ethUtil.toBuffer(sig.s),
+ ethUtil.toBuffer(SignatureType.EIP712),
+ ]);
+ const signatureHex = `0x${signatureBuffer.toString('hex')}`;
+ const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, makerAddress);
+ const isValidSignature = await signatureUtils.isValidSignatureAsync(
+ provider,
+ orderHashHex,
+ signedOrder.signature,
makerAddress,
- takerAddress: constants.NULL_ADDRESS,
- senderAddress: constants.NULL_ADDRESS,
- feeRecipientAddress: constants.NULL_ADDRESS,
- makerAssetData: constants.NULL_ADDRESS,
- takerAssetData: constants.NULL_ADDRESS,
- exchangeAddress: fakeExchangeContractAddress,
- salt: new BigNumber(0),
- makerFee: new BigNumber(0),
- takerFee: new BigNumber(0),
- makerAssetAmount: new BigNumber(0),
- takerAssetAmount: new BigNumber(0),
- expirationTimeSeconds: new BigNumber(0),
- };
+ );
+ expect(signatureHex).to.eq(signedOrder.signature);
+ expect(isValidSignature).to.eq(true);
});
it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => {
const expectedSignature =
diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts
index 6a8100e68..9f4dac58b 100644
--- a/packages/subproviders/src/index.ts
+++ b/packages/subproviders/src/index.ts
@@ -57,4 +57,10 @@ export {
EIP712Parameter,
} from '@0xproject/types';
-export { JSONRPCRequestPayload, Provider, JSONRPCResponsePayload, JSONRPCErrorCallback } from 'ethereum-types';
+export {
+ JSONRPCRequestPayload,
+ Provider,
+ JSONRPCResponsePayload,
+ JSONRPCErrorCallback,
+ JSONRPCResponseError,
+} from 'ethereum-types';
diff --git a/packages/subproviders/src/subproviders/private_key_wallet.ts b/packages/subproviders/src/subproviders/private_key_wallet.ts
index 96e4190ed..e89c4c186 100644
--- a/packages/subproviders/src/subproviders/private_key_wallet.ts
+++ b/packages/subproviders/src/subproviders/private_key_wallet.ts
@@ -106,7 +106,7 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
`Requested to sign message with address: ${address}, instantiated with address: ${this._address}`,
);
}
- const dataBuff = signTypedDataUtils.signTypedDataHash(typedData);
+ const dataBuff = signTypedDataUtils.generateTypedDataHash(typedData);
const sig = ethUtil.ecsign(dataBuff, this._privateKeyBuffer);
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
return rpcSig;
diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts
index 657a59ed0..cd5bcb42f 100644
--- a/packages/utils/src/sign_typed_data_utils.ts
+++ b/packages/utils/src/sign_typed_data_utils.ts
@@ -6,11 +6,11 @@ import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@
export const signTypedDataUtils = {
/**
- * Computes the Sign Typed Data hash
+ * Generates the EIP712 Typed Data hash for signing
* @param typedData An object that conforms to the EIP712TypedData interface
- * @return A Buffer containing the hash of the sign typed data.
+ * @return A Buffer containing the hash of the typed data.
*/
- signTypedDataHash(typedData: EIP712TypedData): Buffer {
+ generateTypedDataHash(typedData: EIP712TypedData): Buffer {
return ethUtil.sha3(
Buffer.concat([
Buffer.from('1901', 'hex'),
diff --git a/packages/utils/test/sign_typed_data_utils_test.ts b/packages/utils/test/sign_typed_data_utils_test.ts
index e1cb4f6e1..dcba08b04 100644
--- a/packages/utils/test/sign_typed_data_utils_test.ts
+++ b/packages/utils/test/sign_typed_data_utils_test.ts
@@ -127,12 +127,12 @@ describe('signTypedDataUtils', () => {
primaryType: 'Order',
};
it('creates a hash of the test sign typed data', () => {
- const hash = signTypedDataUtils.signTypedDataHash(simpleSignTypedData).toString('hex');
+ const hash = signTypedDataUtils.generateTypedDataHash(simpleSignTypedData).toString('hex');
const hashHex = `0x${hash}`;
expect(hashHex).to.be.eq(simpleSignTypedDataHashHex);
});
it('creates a hash of the order sign typed data', () => {
- const hash = signTypedDataUtils.signTypedDataHash(orderSignTypedData).toString('hex');
+ const hash = signTypedDataUtils.generateTypedDataHash(orderSignTypedData).toString('hex');
const hashHex = `0x${hash}`;
expect(hashHex).to.be.eq(orderSignTypedDataHashHex);
});
diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json
index 47f054300..be5c1fef6 100644
--- a/packages/web3-wrapper/CHANGELOG.json
+++ b/packages/web3-wrapper/CHANGELOG.json
@@ -1,5 +1,19 @@
[
{
+ "version": "3.1.0",
+ "changes": [
+ {
+ "note": "Add `signTypedData` to perform EIP712 `eth_signTypedData`.",
+ "pr": 1102
+ },
+ {
+ "note":
+ "Web3Wrapper now throws when an RPC request contains an error field in the response. Previously errors could be swallowed and undefined returned.",
+ "pr": 1102
+ }
+ ]
+ },
+ {
"version": "3.0.3",
"changes": [
{
diff --git a/packages/web3-wrapper/src/web3_wrapper.ts b/packages/web3-wrapper/src/web3_wrapper.ts
index 034bafb5f..726246f1a 100644
--- a/packages/web3-wrapper/src/web3_wrapper.ts
+++ b/packages/web3-wrapper/src/web3_wrapper.ts
@@ -322,7 +322,7 @@ export class Web3Wrapper {
*/
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
assert.isETHAddressHex('address', address);
- assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedData);
+ assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
const signData = await this.sendRawPayloadAsync<string>({
method: 'eth_signTypedData',
params: [address, typedData],
@@ -669,6 +669,9 @@ export class Web3Wrapper {
...payload,
};
const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults);
+ if (response.error) {
+ throw new Error(response.error.message);
+ }
const result = response.result;
return result;
}
diff --git a/packages/web3-wrapper/test/web3_wrapper_test.ts b/packages/web3-wrapper/test/web3_wrapper_test.ts
index 385c469bf..164253777 100644
--- a/packages/web3-wrapper/test/web3_wrapper_test.ts
+++ b/packages/web3-wrapper/test/web3_wrapper_test.ts
@@ -1,5 +1,5 @@
import * as chai from 'chai';
-import { BlockParamLiteral } from 'ethereum-types';
+import { BlockParamLiteral, JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
import * as Ganache from 'ganache-core';
import * as _ from 'lodash';
import 'mocha';
@@ -78,6 +78,19 @@ describe('Web3Wrapper tests', () => {
const signatureLength = 132;
expect(signature.length).to.be.equal(signatureLength);
});
+ it('should throw if the provider returns an error', async () => {
+ const message = '0xdeadbeef';
+ const signer = addresses[1];
+ const fakeProvider = {
+ async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
+ callback(new Error('User denied message signature'));
+ },
+ };
+ const errorWeb3Wrapper = new Web3Wrapper(fakeProvider);
+ expect(errorWeb3Wrapper.signMessageAsync(signer, message)).to.be.rejectedWith(
+ 'User denied message signature',
+ );
+ });
});
describe('#getBlockNumberAsync', () => {
it('get block number', async () => {
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index 652f2eb1d..b1181e4c6 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -17,6 +17,7 @@ import {
MetamaskSubprovider,
RedundantSubprovider,
RPCSubprovider,
+ SignerSubprovider,
Web3ProviderEngine,
} from '@0xproject/subproviders';
import { SignedOrder, Token as ZeroExToken } from '@0xproject/types';
@@ -27,8 +28,6 @@ import * as _ from 'lodash';
import * as moment from 'moment';
import * as React from 'react';
import contract = require('truffle-contract');
-import { tokenAddressOverrides } from 'ts/utils/token_address_overrides';
-
import { BlockchainWatcher } from 'ts/blockchain_watcher';
import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed';
import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted';
@@ -54,6 +53,7 @@ import { backendClient } from 'ts/utils/backend_client';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter';
+import { tokenAddressOverrides } from 'ts/utils/token_address_overrides';
import { utils } from 'ts/utils/utils';
import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
@@ -161,7 +161,13 @@ export class Blockchain {
// We catch all requests involving a users account and send it to the injectedWeb3
// instance. All other requests go to the public hosted node.
const provider = new Web3ProviderEngine();
- provider.addProvider(new MetamaskSubprovider(injectedWeb3.currentProvider));
+ const providerName = this._getNameGivenProvider(injectedWeb3.currentProvider);
+ // Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies)
+ const signerSubprovider =
+ providerName === Providers.Metamask
+ ? new MetamaskSubprovider(injectedWeb3.currentProvider)
+ : new SignerSubprovider(injectedWeb3.currentProvider);
+ provider.addProvider(signerSubprovider);
provider.addProvider(new FilterSubprovider());
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
return new RPCSubprovider(publicNodeUrl);
diff --git a/yarn.lock b/yarn.lock
index 8dac36588..43485109e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5606,19 +5606,6 @@ ethereumjs-wallet@0.6.0:
utf8 "^2.1.1"
uuid "^2.0.1"
-ethereumjs-wallet@~0.6.0:
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda"
- dependencies:
- aes-js "^3.1.1"
- bs58check "^2.1.2"
- ethereumjs-util "^5.2.0"
- hdkey "^1.0.0"
- safe-buffer "^5.1.2"
- scrypt.js "^0.2.0"
- utf8 "^3.0.0"
- uuid "^3.3.2"
-
ethers@3.0.22:
version "3.0.22"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-3.0.22.tgz#7fab1ea16521705837aa43c15831877b2716b436"
@@ -6432,7 +6419,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep:
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
ethereumjs-util "^5.2.0"
ethereumjs-vm "2.3.5"
- ethereumjs-wallet "0.6.0"
+ ethereumjs-wallet "~0.6.0"
fake-merkle-patricia-tree "~1.0.1"
heap "~0.2.6"
js-scrypt "^0.2.0"
@@ -6797,7 +6784,7 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
-globby@^8.0.0, globby@^8.0.1:
+globby@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.1.tgz#b5ad48b8aa80b35b814fc1281ecc851f1d2b5b50"
dependencies:
@@ -8576,10 +8563,6 @@ jsesc@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
-json-buffer@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
-
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@@ -9357,7 +9340,7 @@ lodash@^4.13.1, lodash@^4.15.0:
version "4.17.11"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
+lodash@^4.14.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
@@ -10882,10 +10865,6 @@ p-is-promise@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
-p-lazy@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-lazy/-/p-lazy-1.0.0.tgz#ec53c802f2ee3ac28f166cc82d0b2b02de27a835"
-
p-limit@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
@@ -15834,12 +15813,6 @@ webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
-webpack-addons@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/webpack-addons/-/webpack-addons-1.1.5.tgz#2b178dfe873fb6e75e40a819fa5c26e4a9bc837a"
- dependencies:
- jscodeshift "^0.4.0"
-
webpack-cli@3.1.2, webpack-cli@^3.1.1:
version "3.1.2"
resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz#17d7e01b77f89f884a2bbf9db545f0f6a648e746"
@@ -15855,37 +15828,6 @@ webpack-cli@3.1.2, webpack-cli@^3.1.1:
v8-compile-cache "^2.0.2"
yargs "^12.0.2"
-webpack-cli@^2.0.9:
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-2.1.3.tgz#65d166851abaa56067ef3f716b02a97ba6bbe84d"
- dependencies:
- chalk "^2.3.2"
- cross-spawn "^6.0.5"
- diff "^3.5.0"
- enhanced-resolve "^4.0.0"
- envinfo "^4.4.2"
- glob-all "^3.1.0"
- global-modules "^1.0.0"
- got "^8.2.0"
- import-local "^1.0.0"
- inquirer "^5.1.0"
- interpret "^1.0.4"
- jscodeshift "^0.5.0"
- listr "^0.13.0"
- loader-utils "^1.1.0"
- lodash "^4.17.5"
- log-symbols "^2.2.0"
- mkdirp "^0.5.1"
- p-each-series "^1.0.0"
- p-lazy "^1.0.0"
- prettier "^1.5.3"
- supports-color "^5.3.0"
- v8-compile-cache "^1.1.2"
- webpack-addons "^1.1.5"
- yargs "^11.1.0"
- yeoman-environment "^2.0.0"
- yeoman-generator "^2.0.4"
-
webpack-dev-middleware@3.4.0:
version "3.4.0"
resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890"