Skip to content

Commit 82cdac7

Browse files
Merge pull request #299 from iExecBlockchainComputing/feature/detect-user-rejection
added Web3ProviderError.isUserRejection
2 parents 5179712 + 4343c0e commit 82cdac7

File tree

9 files changed

+212
-21
lines changed

9 files changed

+212
-21
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
## Next
66

7+
### Added
8+
9+
- added key `isUserRejection` in `Web3ProviderError` set to `true` when the error is detected as a user rejection
10+
711
### Changed
812

913
- `iexec_result_storage_proxy` default value is no more set in request params

docs/classes/errors.Web3ProviderCallError.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Web3ProviderCallError encapsulates an error thrown by the web3 provider during a
2121
### Properties
2222

2323
- [cause](errors.Web3ProviderCallError.md#cause)
24+
- [isUserRejection](errors.Web3ProviderCallError.md#isuserrejection)
2425
- [originalError](errors.Web3ProviderCallError.md#originalerror)
2526

2627
## Constructors
@@ -58,6 +59,18 @@ The original Error object that caused this web3 provider error.
5859

5960
___
6061

62+
### isUserRejection
63+
64+
`Optional` **isUserRejection**: `boolean`
65+
66+
Wether the error was caused by a user rejection
67+
68+
#### Inherited from
69+
70+
[Web3ProviderError](errors.Web3ProviderError.md).[isUserRejection](errors.Web3ProviderError.md#isuserrejection)
71+
72+
___
73+
6174
### originalError
6275

6376
**originalError**: `Error`

docs/classes/errors.Web3ProviderError.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Web3ProviderError encapsulates an error thrown by the web3 provider.
2727
### Properties
2828

2929
- [cause](errors.Web3ProviderError.md#cause)
30+
- [isUserRejection](errors.Web3ProviderError.md#isuserrejection)
3031
- [originalError](errors.Web3ProviderError.md#originalerror)
3132

3233
## Constructors
@@ -60,6 +61,14 @@ The original Error object that caused this web3 provider error.
6061

6162
___
6263

64+
### isUserRejection
65+
66+
`Optional` **isUserRejection**: `boolean`
67+
68+
Wether the error was caused by a user rejection
69+
70+
___
71+
6372
### originalError
6473

6574
**originalError**: `Error`

docs/classes/errors.Web3ProviderSendError.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Web3ProviderSendError encapsulates an error thrown by the web3 provider during a
2121
### Properties
2222

2323
- [cause](errors.Web3ProviderSendError.md#cause)
24+
- [isUserRejection](errors.Web3ProviderSendError.md#isuserrejection)
2425
- [originalError](errors.Web3ProviderSendError.md#originalerror)
2526

2627
## Constructors
@@ -58,6 +59,18 @@ The original Error object that caused this web3 provider error.
5859

5960
___
6061

62+
### isUserRejection
63+
64+
`Optional` **isUserRejection**: `boolean`
65+
66+
Wether the error was caused by a user rejection
67+
68+
#### Inherited from
69+
70+
[Web3ProviderError](errors.Web3ProviderError.md).[isUserRejection](errors.Web3ProviderError.md#isuserrejection)
71+
72+
___
73+
6174
### originalError
6275

6376
**originalError**: `Error`

docs/classes/errors.Web3ProviderSignMessageError.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Web3ProviderSignMessageError encapsulates an error thrown by the web3 provider d
2121
### Properties
2222

2323
- [cause](errors.Web3ProviderSignMessageError.md#cause)
24+
- [isUserRejection](errors.Web3ProviderSignMessageError.md#isuserrejection)
2425
- [originalError](errors.Web3ProviderSignMessageError.md#originalerror)
2526

2627
## Constructors
@@ -58,6 +59,18 @@ The original Error object that caused this web3 provider error.
5859

5960
___
6061

62+
### isUserRejection
63+
64+
`Optional` **isUserRejection**: `boolean`
65+
66+
Wether the error was caused by a user rejection
67+
68+
#### Inherited from
69+
70+
[Web3ProviderError](errors.Web3ProviderError.md).[isUserRejection](errors.Web3ProviderError.md#isuserrejection)
71+
72+
___
73+
6174
### originalError
6275

6376
**originalError**: `Error`

src/common/utils/errors.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export class Web3ProviderError extends Error {
2929
if (originalError && typeof originalError === 'object') {
3030
Object.assign(this, getPropsToCopy(originalError));
3131
}
32+
// detect user rejection from ethers error code
33+
if (originalError?.code === 'ACTION_REJECTED') {
34+
this.isUserRejection = true;
35+
}
3236
}
3337
}
3438

src/lib/errors.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export class Web3ProviderError extends Error {
2929
* The original Error object that caused this web3 provider error.
3030
*/
3131
cause: Error;
32+
/**
33+
* Wether the error was caused by a user rejection
34+
*/
35+
isUserRejection?: boolean;
3236
}
3337
/**
3438
* Web3ProviderCallError encapsulates an error thrown by the web3 provider during a web3 call.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// @jest/global comes with jest
2+
// eslint-disable-next-line import/no-extraneous-dependencies
3+
import { describe, test, expect } from '@jest/globals';
4+
import { BrowserProvider } from 'ethers';
5+
import {
6+
getRandomWallet,
7+
InjectedProvider,
8+
NULL_ADDRESS,
9+
} from '../../test-utils.js';
10+
import {
11+
wrapPersonalSign,
12+
wrapSignTypedData,
13+
wrapSend,
14+
} from '../../../src/common/utils/errorWrappers.js';
15+
import {
16+
Web3ProviderSendError,
17+
Web3ProviderSignMessageError,
18+
} from '../../../src/lib/errors.js';
19+
20+
describe('wrapPersonalSign', () => {
21+
test('detects user signature rejection', async () => {
22+
const userRejectSigner = await new BrowserProvider(
23+
new InjectedProvider(
24+
'https://bellecour.iex.ec',
25+
getRandomWallet().privateKey,
26+
{
27+
mockUserRejection: true,
28+
},
29+
),
30+
).getSigner();
31+
const rejectError = await wrapPersonalSign(
32+
userRejectSigner.signMessage('foo'),
33+
).catch((e) => e);
34+
expect(rejectError).toBeInstanceOf(Web3ProviderSignMessageError);
35+
expect(rejectError.isUserRejection).toBe(true);
36+
37+
const errorSigner = await new BrowserProvider(
38+
new InjectedProvider(
39+
'https://bellecour.iex.ec',
40+
getRandomWallet().privateKey,
41+
{
42+
mockError: true,
43+
},
44+
),
45+
).getSigner();
46+
const unknownError = await wrapPersonalSign(
47+
errorSigner.signMessage('foo'),
48+
).catch((e) => e);
49+
expect(unknownError).toBeInstanceOf(Web3ProviderSignMessageError);
50+
expect(unknownError.isUserRejection).toBe(undefined);
51+
});
52+
});
53+
54+
describe('wrapSignTypedData', () => {
55+
test('detects user signature rejection', async () => {
56+
const userRejectSigner = await new BrowserProvider(
57+
new InjectedProvider(
58+
'https://bellecour.iex.ec',
59+
getRandomWallet().privateKey,
60+
{
61+
mockUserRejection: true,
62+
},
63+
),
64+
).getSigner();
65+
const rejectError = await wrapSignTypedData(
66+
userRejectSigner.signTypedData(
67+
{ name: 'domain' },
68+
{ custom: [{ name: 'foo', type: 'string' }] },
69+
{ foo: 'bar' },
70+
),
71+
).catch((e) => e);
72+
expect(rejectError).toBeInstanceOf(Web3ProviderSignMessageError);
73+
expect(rejectError.isUserRejection).toBe(true);
74+
75+
const errorSigner = await new BrowserProvider(
76+
new InjectedProvider(
77+
'https://bellecour.iex.ec',
78+
getRandomWallet().privateKey,
79+
{
80+
mockError: true,
81+
},
82+
),
83+
).getSigner();
84+
const unknownError = await wrapSignTypedData(
85+
errorSigner.signTypedData(
86+
{ name: 'domain' },
87+
{ custom: [{ name: 'foo', type: 'string' }] },
88+
{ foo: 'bar' },
89+
),
90+
).catch((e) => e);
91+
expect(unknownError).toBeInstanceOf(Web3ProviderSignMessageError);
92+
expect(unknownError.isUserRejection).toBe(undefined);
93+
});
94+
});
95+
96+
describe('wrapSend', () => {
97+
test('detects user signature rejection', async () => {
98+
const userRejectSigner = await new BrowserProvider(
99+
new InjectedProvider(
100+
'https://bellecour.iex.ec',
101+
getRandomWallet().privateKey,
102+
{
103+
mockUserRejection: true,
104+
},
105+
),
106+
).getSigner();
107+
const rejectError = await wrapSend(
108+
userRejectSigner.sendTransaction({ to: NULL_ADDRESS }),
109+
).catch((e) => e);
110+
expect(rejectError).toBeInstanceOf(Web3ProviderSendError);
111+
expect(rejectError.isUserRejection).toBe(true);
112+
113+
const errorSigner = await new BrowserProvider(
114+
new InjectedProvider(
115+
'https://bellecour.iex.ec',
116+
getRandomWallet().privateKey,
117+
{
118+
mockError: true,
119+
},
120+
),
121+
).getSigner();
122+
const unknownError = await wrapSend(
123+
errorSigner.sendTransaction({ to: NULL_ADDRESS }),
124+
).catch((e) => e);
125+
expect(unknownError).toBeInstanceOf(Web3ProviderSendError);
126+
expect(unknownError.isUserRejection).toBe(undefined);
127+
});
128+
});

test/test-utils.js

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -148,53 +148,56 @@ export const getRandomAddress = () => getRandomWallet().address;
148148

149149
export const getRandomBytes32 = () => hexlify(randomBytes(32));
150150
export class InjectedProvider {
151-
constructor(rpcUrl, privateKey) {
152-
this.signer = new Wallet(
153-
privateKey,
154-
new JsonRpcProvider(rpcUrl, undefined, { pollingInterval: 100 }), // fast polling for tests
155-
);
151+
constructor(rpcUrl, privateKey, { mockUserRejection, mockError } = {}) {
152+
this.provider = new JsonRpcProvider(rpcUrl, undefined, {
153+
pollingInterval: 100,
154+
}); // fast polling for tests
155+
this.signer = new Wallet(privateKey, this.provider);
156+
this.mockUserRejection = mockUserRejection;
157+
this.mockError = mockError;
156158
}
157159

158160
async request(args) {
159161
const { method, params } = args;
160-
let rpcPromise;
161162
switch (method) {
162163
case 'eth_requestAccounts':
163164
case 'eth_accounts':
164-
rpcPromise = Promise.resolve([this.signer.address]);
165-
break;
165+
return Promise.resolve([this.signer.address]);
166166
case 'eth_chainId':
167-
rpcPromise = this.signer.provider
167+
return this.provider
168168
.getNetwork()
169169
.then((network) => network.chainId.toString());
170-
break;
170+
case 'eth_blockNumber':
171+
return this.provider.getBlockNumber();
171172
case 'personal_sign':
172-
rpcPromise = this.signer.signMessage(params[0]);
173-
break;
173+
if (this.mockError) return Promise.reject(Error('error'));
174+
if (this.mockUserRejection) return Promise.reject(Error('user denied'));
175+
return this.signer.signMessage(params[0]);
174176
case 'eth_signTypedData_v3':
175177
case 'eth_signTypedData_v4':
176-
rpcPromise = (async () => {
178+
if (this.mockError) return Promise.reject(Error('error'));
179+
if (this.mockUserRejection) return Promise.reject(Error('user denied'));
180+
return (async () => {
177181
const typedData = JSON.parse(params[1]);
178182
const { EIP712Domain, ...types } = typedData.types;
179183
const { message, domain } = typedData;
180184
return this.signer.signTypedData(domain, types, message);
181185
})();
182-
break;
183186
case 'eth_call':
184-
rpcPromise = this.provider.call(params[0]);
185-
break;
187+
return this.provider.call(params[0]);
188+
case 'eth_estimateGas':
189+
return this.provider.estimateGas(params[0]);
186190
case 'eth_sendTransaction':
187-
rpcPromise = (async () => {
191+
if (this.mockError) return Promise.reject(Error('error'));
192+
if (this.mockUserRejection) return Promise.reject(Error('user denied'));
193+
return (async () => {
188194
const { gas, ...gasStripped } = params[0];
189195
const transaction = await this.signer.sendTransaction(gasStripped);
190196
return transaction.hash;
191197
})();
192-
break;
193198
default:
194-
rpcPromise = Promise.reject(Error('not implemented'));
195-
break;
199+
return Promise.reject(Error('not implemented'));
196200
}
197-
return rpcPromise;
198201
}
199202
}
200203

0 commit comments

Comments
 (0)