Skip to content

Commit 62c0f20

Browse files
authored
Merge pull request #239 from oasisprotocol/CU-8692tq8gw_Remove-Ethers-5_Xi-Zhang
Remove Ethers v5
2 parents add1b7f + 5fc1d83 commit 62c0f20

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+6844
-3660
lines changed

.github/workflows/contracts-test.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,9 @@ jobs:
7979
- name: hardhat test examples/onchain-signer
8080
working-directory: examples/onchain-signer
8181
run: pnpm run test --network sapphire-localnet
82+
- name: ethersv5-ts-esm
83+
working-directory: examples/ethersv5-ts-esm
84+
run: pnpm run test
85+
- name: ethersv6-ts-esm
86+
working-directory: examples/ethersv6-ts-esm
87+
run: pnpm run test

clients/js/package.json

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,42 +34,36 @@
3434
"lint": "prettier --cache --check . && eslint --ignore-path .gitignore .",
3535
"format": "prettier --cache --write . && eslint --ignore-path .gitignore --fix .",
3636
"build": "npm run build:esm && npm run build:cjs",
37+
"clean": "rm -rf lib",
3738
"build:esm": "tsc -p ./tsconfig.json",
3839
"build:cjs": "tsc -p ./tsconfig.cjs.json && node scripts/rename-cjs",
3940
"test": "jest",
4041
"coverage": "jest --coverage",
4142
"prepublishOnly": "npm run build"
4243
},
4344
"dependencies": {
44-
"@ethersproject/abstract-provider": "^5.7.0",
45-
"@ethersproject/abstract-signer": "^5.7.0",
46-
"@ethersproject/bignumber": "^5.7.0",
47-
"@ethersproject/bytes": "^5.7.0",
48-
"@ethersproject/providers": "^5.7.1",
49-
"@ethersproject/rlp": "^5.7.0",
5045
"@oasisprotocol/deoxysii": "^0.0.5",
5146
"cborg": "^1.9.5",
52-
"ethers6": "npm:ethers@^6.6.1",
47+
"ethers": "^6.6.1",
5348
"js-sha512": "^0.8.0",
5449
"tweetnacl": "^1.0.3",
5550
"type-fest": "^2.19.0"
5651
},
5752
"devDependencies": {
58-
"@ethersproject/transactions": "^5.7.0",
59-
"@types/jest": "^28.1.8",
53+
"@types/jest": "^29.5.11",
6054
"@types/node": "^18.7.18",
6155
"@types/node-fetch": "^2.6.2",
6256
"@typescript-eslint/eslint-plugin": "^5.38.0",
6357
"@typescript-eslint/parser": "^5.38.0",
6458
"eslint": "^8.23.1",
6559
"eslint-config-prettier": "^8.5.0",
66-
"ethers5": "npm:ethers@^5.7.2",
67-
"jest": "^28.1.3",
68-
"nock": "^13.2.9",
60+
"jest": "^29.7.0",
61+
"nock": "^13.4.0",
6962
"node-fetch": "^2.6.7",
7063
"prettier": "^2.7.1",
71-
"ts-jest": "^28.0.8",
72-
"typedoc": "^0.25.1",
64+
"ts-jest": "^29.1.1",
65+
"ts-node": "^10.9.2",
66+
"typedoc": "^0.25.4",
7367
"typescript": "^4.8.3"
7468
}
7569
}

clients/js/scripts/proxy.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { createServer, IncomingMessage, ServerResponse } from 'node:http';
2+
3+
import { decodeRlp, getBytes } from 'ethers';
4+
5+
import * as cborg from 'cborg';
6+
7+
import fetch from 'node-fetch';
8+
import { assert } from 'console';
9+
10+
async function getBody(request: IncomingMessage): Promise<string> {
11+
return new Promise((resolve) => {
12+
const bodyParts: Uint8Array[] = [];
13+
let body: string;
14+
request
15+
.on('data', (chunk) => {
16+
bodyParts.push(chunk);
17+
})
18+
.on('end', () => {
19+
body = Buffer.concat(bodyParts).toString();
20+
resolve(body);
21+
});
22+
});
23+
}
24+
25+
createServer(onRequest).listen(3000);
26+
27+
interface JSONRPCRequest {
28+
jsonrpc: string;
29+
method: string;
30+
params: any[];
31+
id: number;
32+
}
33+
34+
async function onRequest(req: IncomingMessage, response: ServerResponse) {
35+
if (req.method != 'POST') {
36+
response.writeHead(500, 'Not POST!');
37+
response.end();
38+
return;
39+
}
40+
41+
const inputBody = JSON.parse(await getBody(req)) as JSONRPCRequest;
42+
const encryptableMethods = [
43+
'eth_estimateGas',
44+
'eth_call',
45+
'eth_sendRawTransaction',
46+
];
47+
const loggedMethods = encryptableMethods.concat(['oasis_callDataPublicKey']);
48+
49+
let bodies: JSONRPCRequest[];
50+
if (Array.isArray(inputBody)) {
51+
bodies = inputBody;
52+
} else {
53+
bodies = [inputBody];
54+
}
55+
56+
for (const body of bodies) {
57+
const log = loggedMethods.includes(body.method);
58+
59+
if (log) {
60+
if (body.method == 'oasis_callDataPublicKey') {
61+
console.log(req.method, req.url, body.method);
62+
} else if (
63+
body.method == 'eth_estimateGas' ||
64+
body.method == 'eth_call'
65+
) {
66+
try {
67+
const x = getBytes(body.params[0].data);
68+
const y = cborg.decode(x);
69+
// Verify envelope format == 1 (encrypted)
70+
if ('data' in y) {
71+
// EIP-712 signed queries are wrapped as follows:
72+
// {data: {body{pk:,data:,nonce:},format:},leash:{nonce:,block_hash:,block_range:,block_number:},signature:}
73+
assert(y.data.format == 1);
74+
} else {
75+
assert(y.format == 1);
76+
}
77+
console.log('ENCRYPTED', req.method, req.url, body.method);
78+
} catch (e: any) {
79+
console.log('NOT ENCRYPTED', req.method, req.url, body.method);
80+
}
81+
} else if (body.method == 'eth_sendRawTransaction') {
82+
try {
83+
const x = getBytes(body.params[0]);
84+
const y = decodeRlp(x) as string[]; //console.log(pj);
85+
const z = cborg.decode(getBytes(y[5]));
86+
assert(z.format == 1); // Verify envelope format == 1 (encrypted)
87+
console.log('ENCRYPTED', req.method, req.url, body.method);
88+
} catch (e: any) {
89+
console.log(
90+
'NOT ENCRYPTED',
91+
req.method,
92+
req.url,
93+
body.method,
94+
body.params,
95+
);
96+
}
97+
}
98+
}
99+
}
100+
101+
const pr = await fetch('http://127.0.0.1:8545/', {
102+
method: 'POST',
103+
body: JSON.stringify(inputBody),
104+
headers: { 'Content-Type': 'application/json' },
105+
});
106+
107+
const pj = await pr.json();
108+
109+
response.writeHead(200, 'OK');
110+
response.write(JSON.stringify(pj));
111+
response.end();
112+
}

clients/js/src/cipher.ts

Lines changed: 9 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
import {
2-
BytesLike,
3-
arrayify,
4-
hexlify,
5-
isBytesLike,
6-
} from '@ethersproject/bytes';
71
import * as cbor from 'cborg';
2+
import { BytesLike, isBytesLike, hexlify, getBytes } from 'ethers';
83
import deoxysii from '@oasisprotocol/deoxysii';
9-
import { IncomingMessage } from 'http';
104
import { sha512_256 } from 'js-sha512';
115
import nacl, { BoxKeyPair } from 'tweetnacl';
126
import { Promisable } from 'type-fest';
137

14-
import { CallError, NETWORKS, OASIS_CALL_DATA_PUBLIC_KEY } from './index.js';
8+
import { CallError } from './index.js';
159

1610
export enum Kind {
1711
Plain = 0,
@@ -66,7 +60,7 @@ export abstract class Cipher {
6660
throw new Error('Attempted to sign tx having non-byteslike data.');
6761
}
6862
if (plaintext.length === 0) return; // Txs without data are just balance transfers, and all data in those is public.
69-
const { data, nonce } = await this.encryptCallData(arrayify(plaintext));
63+
const { data, nonce } = await this.encryptCallData(getBytes(plaintext));
7064
const [format, pk] = await Promise.all([this.kind, this.publicKey]);
7165
const body = pk.length && nonce.length ? { pk, nonce, data } : data;
7266
if (format === Kind.Plain) return { body };
@@ -114,7 +108,7 @@ export abstract class Cipher {
114108
/** Decrypts the data contained within a hex-encoded serialized envelope. */
115109
public async decryptEncoded(callResult: BytesLike): Promise<string> {
116110
return hexlify(
117-
await this.decryptCallResult(cbor.decode(arrayify(callResult))),
111+
await this.decryptCallResult(cbor.decode(getBytes(callResult))),
118112
);
119113
}
120114

@@ -126,10 +120,10 @@ export abstract class Cipher {
126120
}
127121
if (res.fail) throw new CallError(formatFailure(res.fail), res.fail);
128122
if (res.ok && (typeof res.ok === 'string' || res.ok instanceof Uint8Array))
129-
return arrayify(res.ok);
123+
return getBytes(res.ok);
130124
const { nonce, data } = (res.ok as AeadEnvelope) ?? res.unknown;
131125
const inner = cbor.decode(await this.decrypt(nonce, data));
132-
if (inner.ok) return arrayify(inner.ok);
126+
if (inner.ok) return getBytes(inner.ok);
133127
if (inner.fail) throw new CallError(formatFailure(inner.fail), inner.fail);
134128
throw new CallError(
135129
`Unexpected inner call result: ${JSON.stringify(inner)}`,
@@ -184,18 +178,15 @@ export class X25519DeoxysII extends Cipher {
184178
/** Creates a new cipher using an ephemeral keypair stored in memory. */
185179
static ephemeral(peerPublicKey: BytesLike): X25519DeoxysII {
186180
const keypair = nacl.box.keyPair();
187-
return new X25519DeoxysII(
188-
keypair,
189-
arrayify(peerPublicKey, { allowMissingPrefix: true }),
190-
);
181+
return new X25519DeoxysII(keypair, getBytes(peerPublicKey));
191182
}
192183

193184
static fromSecretKey(
194185
secretKey: BytesLike,
195186
peerPublicKey: BytesLike,
196187
): X25519DeoxysII {
197-
const keypair = nacl.box.keyPair.fromSecretKey(arrayify(secretKey));
198-
return new X25519DeoxysII(keypair, arrayify(peerPublicKey));
188+
const keypair = nacl.box.keyPair.fromSecretKey(getBytes(secretKey));
189+
return new X25519DeoxysII(keypair, getBytes(peerPublicKey));
199190
}
200191

201192
public constructor(keypair: BoxKeyPair, peerPublicKey: Uint8Array) {
@@ -277,78 +268,3 @@ export function lazy(generator: () => Promisable<Cipher>): Cipher {
277268
},
278269
) as Cipher;
279270
}
280-
281-
export async function fetchRuntimePublicKeyByChainId(
282-
chainId: number,
283-
opts?: { fetch?: typeof fetch },
284-
): Promise<Uint8Array> {
285-
const { defaultGateway: gatewayUrl } = NETWORKS[chainId];
286-
if (!gatewayUrl)
287-
throw new Error(
288-
`Unable to fetch runtime public key for network with unknown ID: ${chainId}.`,
289-
);
290-
const fetchImpl = globalThis?.fetch ?? opts?.fetch;
291-
const res = await (fetchImpl
292-
? fetchRuntimePublicKeyBrowser(gatewayUrl, fetchImpl)
293-
: fetchRuntimePublicKeyNode(gatewayUrl));
294-
return arrayify(res.result.key);
295-
}
296-
297-
type CallDataPublicKeyResponse = {
298-
result: { key: string; checksum: string; signature: string };
299-
};
300-
301-
async function fetchRuntimePublicKeyNode(
302-
gwUrl: string,
303-
): Promise<CallDataPublicKeyResponse> {
304-
// Import http or https, depending on the URI scheme.
305-
const https = await import(/* webpackIgnore: true */ gwUrl.split(':')[0]);
306-
307-
const body = makeCallDataPublicKeyBody();
308-
return new Promise((resolve, reject) => {
309-
const opts = {
310-
method: 'POST',
311-
headers: {
312-
'content-type': 'application/json',
313-
'content-length': body.length,
314-
},
315-
};
316-
const req = https.request(gwUrl, opts, (res: IncomingMessage) => {
317-
const chunks: Buffer[] = [];
318-
res.on('error', (err) => reject(err));
319-
res.on('data', (chunk) => chunks.push(chunk));
320-
res.on('end', () => {
321-
resolve(JSON.parse(Buffer.concat(chunks).toString()));
322-
});
323-
});
324-
req.on('error', (err: Error) => reject(err));
325-
req.write(body);
326-
req.end();
327-
});
328-
}
329-
330-
async function fetchRuntimePublicKeyBrowser(
331-
gwUrl: string,
332-
fetchImpl: typeof fetch,
333-
): Promise<CallDataPublicKeyResponse> {
334-
const res = await fetchImpl(gwUrl, {
335-
method: 'POST',
336-
headers: {
337-
'content-type': 'application/json',
338-
},
339-
body: makeCallDataPublicKeyBody(),
340-
});
341-
if (!res.ok) {
342-
throw new CallError('Failed to fetch runtime public key.', res);
343-
}
344-
return await res.json();
345-
}
346-
347-
function makeCallDataPublicKeyBody(): string {
348-
return JSON.stringify({
349-
jsonrpc: '2.0',
350-
id: Math.floor(Math.random() * 1e9),
351-
method: OASIS_CALL_DATA_PUBLIC_KEY,
352-
params: [],
353-
});
354-
}

0 commit comments

Comments
 (0)