Skip to content

Commit

Permalink
Merge pull request #267 from oasisprotocol/CedarMist/clients/js/v1.3.1
Browse files Browse the repository at this point in the history
clients/js: added regression tests for ethers v5 & ethers v6
  • Loading branch information
CedarMist authored Jan 29, 2024
2 parents 53a17d1 + 13fd3c6 commit d20b73d
Show file tree
Hide file tree
Showing 21 changed files with 264 additions and 1,650 deletions.
1 change: 1 addition & 0 deletions clients/js/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ module.exports = {
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
eqeqeq: ['warn', 'always'],
},
};
17 changes: 17 additions & 0 deletions clients/js/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ The format is inspired by [Keep a Changelog].

[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/

## 1.3.1 (2024-01-26)

### Fixed

- Ethers v6 signed queries
- Removed ethers & @noble/hashes from peer dependencies

### Deprecated

- Web3.js is not supported

## 1.3.0 (2024-01-24)

### Changed

- Uses Ethers v6 behind the scenes

## 1.2.0 (2023-11-15)

### Added
Expand Down
15 changes: 3 additions & 12 deletions clients/js/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Sapphire ParaTime Compat Lib

[@oasisprotocol/sapphire-paratime] makes it easy to port your dapp to the [Sapphire ParaTime]
by wrapping your existing `ethers.Provider`/`window.ethereum`/`web3.providers.*`.
by wrapping your existing `ethers.Provider`/`window.ethereum`.
Once you wrap your provider, you can use Sapphire just like you would use Ethereum.

[@oasisprotocol/sapphire-paratime]: https://www.npmjs.com/package/@oasisprotocol/sapphire-paratime
[sapphire paratime]: https://docs.oasis.io/dapp/sapphire/

_If your dapp doesn't port in under 10 minutes, it's a bug!_
If you have more than a little trouble, please file an issue.
_If your dapp doesn't port in under 10 minutes, it's a bug!_<br />
If you have more than a little trouble, please file an issue.<br />
There should be _no_ reason _not_ to use the Sapphire ParaTime!

## Usage
Expand Down Expand Up @@ -50,15 +50,6 @@ const provider = sapphire.wrap(
);
```

### web3.js

```ts
import Web3 from 'web3';
import * as sapphire from '@oasisprotocol/sapphire-paratime';

web3.setProvider(sapphire.wrap(web3.currentProvider));
```

### [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)

```ts
Expand Down
15 changes: 6 additions & 9 deletions clients/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"type": "module",
"name": "@oasisprotocol/sapphire-paratime",
"license": "Apache-2.0",
"version": "1.3.0",
"version": "1.3.1",
"description": "The Sapphire ParaTime Web3 integration library.",
"homepage": "https://github.com/oasisprotocol/sapphire-paratime/tree/main/clients/js",
"repository": {
Expand Down Expand Up @@ -39,15 +39,16 @@
"build:cjs": "tsc -p ./tsconfig.cjs.json && node scripts/rename-cjs",
"test": "jest",
"coverage": "jest --coverage",
"prepublishOnly": "npm run build"
"prepublishOnly": "npm run build",
"proxy": "node --loader ts-node/esm scripts/proxy.ts"
},
"dependencies": {
"@oasisprotocol/deoxysii": "0.0.5",
"cborg": "^1.10.2",
"ethers": "^6.10.0",
"cborg": "1.10.2",
"ethers": "6.10.0",
"@noble/hashes": "1.3.2",
"tweetnacl": "1.0.3",
"type-fest": "^2.19.0"
"type-fest": "2.19.0"
},
"devDependencies": {
"@types/jest": "^29.5.11",
Expand All @@ -65,9 +66,5 @@
"ts-node": "^10.9.2",
"typedoc": "^0.25.4",
"typescript": "^4.8.3"
},
"peerDependencies": {
"ethers": "6.x",
"@noble/hashes": "1.x"
}
}
66 changes: 54 additions & 12 deletions clients/js/scripts/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ async function getBody(request: IncomingMessage): Promise<string> {
});
}

createServer(onRequest).listen(3000);
const LISTEN_PORT = 3000;
const DIE_ON_UNENCRYPTED = true;
const UPSTREAM_URL = 'http://127.0.0.1:8545';
const SHOW_ENCRYPTED_RESULTS = true;

console.log('DIE_ON_UNENCRYPTED', DIE_ON_UNENCRYPTED);
console.log('UPSTREAM_URL', UPSTREAM_URL);
console.log('LISTEN_PORT', LISTEN_PORT);
console.log('SHOW_ENCRYPTED_RESULTS', SHOW_ENCRYPTED_RESULTS);

createServer(onRequest).listen(LISTEN_PORT);

interface JSONRPCRequest {
jsonrpc: string;
Expand All @@ -32,7 +42,7 @@ interface JSONRPCRequest {
}

async function onRequest(req: IncomingMessage, response: ServerResponse) {
if (req.method != 'POST') {
if (req.method !== 'POST') {
response.writeHead(500, 'Not POST!');
response.end();
return;
Expand All @@ -53,39 +63,68 @@ async function onRequest(req: IncomingMessage, response: ServerResponse) {
bodies = [inputBody];
}

let showResult = false;
for (const body of bodies) {
const log = loggedMethods.includes(body.method);

if (log) {
if (body.method == 'oasis_callDataPublicKey') {
if (body.method === 'oasis_callDataPublicKey') {
console.log(req.method, req.url, body.method);
} else if (
body.method == 'eth_estimateGas' ||
body.method == 'eth_call'
body.method === 'eth_estimateGas' ||
body.method === 'eth_call'
) {
let isSignedQuery = false;
try {
const x = getBytes(body.params[0].data);
const y = cborg.decode(x);
// Verify envelope format == 1 (encrypted)
if ('data' in y) {
// EIP-712 signed queries are wrapped as follows:
// {data: {body{pk:,data:,nonce:},format:},leash:{nonce:,block_hash:,block_range:,block_number:},signature:}
assert(y.data.format == 1);
assert(y.data.format === 1);
isSignedQuery = true;
} else {
assert(y.format == 1);
assert(y.format === 1);
}
console.log('ENCRYPTED', req.method, req.url, body.method);
console.log(
'ENCRYPTED' + (isSignedQuery ? ' SIGNED QUERY' : ''),
req.method,
req.url,
body.method,
);
showResult = true;
} catch (e: any) {
console.log('NOT ENCRYPTED', req.method, req.url, body.method);
if (DIE_ON_UNENCRYPTED) {
console.log(e);
console.log(body);
throw new Error(
`NOT ENCRYPTED ${req.method} ${req.url} ${body.method}`,
);
}
console.log(
'NOT ENCRYPTED' + (isSignedQuery ? ' SIGNED QUERY' : ''),
req.method,
req.url,
body.method,
);
}
} else if (body.method == 'eth_sendRawTransaction') {
} else if (body.method === 'eth_sendRawTransaction') {
try {
const x = getBytes(body.params[0]);
const y = decodeRlp(x) as string[]; //console.log(pj);
const z = cborg.decode(getBytes(y[5]));
assert(z.format == 1); // Verify envelope format == 1 (encrypted)
assert(z.format === 1); // Verify envelope format == 1 (encrypted)
console.log('ENCRYPTED', req.method, req.url, body.method);
showResult = true;
} catch (e: any) {
if (DIE_ON_UNENCRYPTED) {
console.log(e);
console.log(body);
throw new Error(
`NOT ENCRYPTED ${req.method} ${req.url} ${body.method}`,
);
}
console.log(
'NOT ENCRYPTED',
req.method,
Expand All @@ -98,13 +137,16 @@ async function onRequest(req: IncomingMessage, response: ServerResponse) {
}
}

const pr = await fetch('http://127.0.0.1:8545/', {
const pr = await fetch(UPSTREAM_URL, {
method: 'POST',
body: JSON.stringify(inputBody),
headers: { 'Content-Type': 'application/json' },
});

const pj = await pr.json();
if (SHOW_ENCRYPTED_RESULTS && showResult) {
console.log(' - RESULT', pj);
}

response.writeHead(200, 'OK');
response.write(JSON.stringify(pj));
Expand Down
32 changes: 21 additions & 11 deletions clients/js/src/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ function fillOptions(
* ethers.providers.Web3Provider(window.ethereum)
* ethers.Wallet(privateKey)
* ethers.getDefaultProvider(NETWORKS.testnet.defaultGateway)
* web3.currentProvider
* window.ethereum
* a Web3 gateway URL
* ```
Expand Down Expand Up @@ -166,7 +165,9 @@ function hookEIP1193Request(
const signer = await provider.getSigner();
const { method, params } = await prepareRequest(args, signer, options);
const res = await signer.provider.send(method, params ?? []);
if (method === 'eth_call') return options.cipher.decryptEncoded(res);
if (method === 'eth_call') {
return options.cipher.decryptEncoded(res);
}
return res;
};
}
Expand Down Expand Up @@ -294,8 +295,8 @@ function isEthers5Signer(upstream: object): upstream is Ethers5Signer {
function isEthers6Signer(upstream: object): upstream is Signer {
return (
upstream instanceof AbstractSigner ||
(Reflect.get(upstream, 'signTypedData') &&
Reflect.get(upstream, 'signTransaction'))
(Reflect.get(upstream, 'signTypedData') !== undefined &&
Reflect.get(upstream, 'signTransaction') !== undefined)
);
}

Expand All @@ -308,7 +309,6 @@ function isEthers5Provider(upstream: object): upstream is Ethers5Signer {
}

function isEthers6Provider(upstream: object): upstream is Provider {
//
return (
upstream instanceof AbstractProvider ||
(Reflect.get(upstream, 'waitForBlock') &&
Expand Down Expand Up @@ -339,12 +339,20 @@ function hookEthersCall(
call.data ?? new Uint8Array(),
);
}
return runner[method]!({
const result = await runner[method]!({
...call,
data: hexlify(call_data!),
});
return result;
};
return async (call) => {
// Ethers v6 uses `populateCall` internally to fill in the `from` field etc.
// It's necessary to call this, if it exists, otherwise signed queries won't work
const populateCall = Reflect.get(runner, 'populateCall');
if (populateCall !== undefined) {
call = await populateCall.bind(runner)(call);
}

let res: string;
const is_already_enveloped = isCalldataEnveloped(call.data!, true);
if (!is_already_enveloped && isEthersSigner(runner)) {
Expand All @@ -366,7 +374,7 @@ function hookEthersCall(
}
// NOTE: if it's already enveloped, caller will decrypt it (not us)
if (!is_already_enveloped && typeof res === 'string') {
return options.cipher.decryptEncoded(res);
return await options.cipher.decryptEncoded(res);
}
return res;
};
Expand All @@ -376,7 +384,9 @@ type EthersCall = (tx: EthCall | TransactionRequest) => Promise<unknown>;

function hookEthersSend<C>(send: C, options: SapphireWrapOptions): C {
return (async (tx: EthCall | TransactionRequest, ...rest: any[]) => {
if (tx.data) tx.data = await options.cipher.encryptEncode(tx.data);
if (tx.data) {
tx.data = await options.cipher.encryptEncode(tx.data);
}
return (send as any)(tx, ...rest);
}) as C;
}
Expand Down Expand Up @@ -452,7 +462,7 @@ async function repackRawTx(

// When transaction is signed by another keypair and we don't have that signer
// bypass re-packing, this allows repacking to pass-thru pre-signed txs
if (tx.isSigned() && (!signer || (await signer!.getAddress()) != tx.from!)) {
if (tx.isSigned() && (!signer || (await signer!.getAddress()) !== tx.from!)) {
return raw;
}

Expand Down Expand Up @@ -516,7 +526,7 @@ function envelopeFormatOk(envelope: Envelope): boolean {

if (!body) return false;

if (format != null && format !== CipherKind.Plain) {
if (format !== null && format !== CipherKind.Plain) {
if (isBytesLike(body)) return false;

if (!isBytesLike(body.data)) return false;
Expand Down Expand Up @@ -611,7 +621,7 @@ export async function fetchRuntimePublicKeyByChainId(
}

function fromQuantity(x: number | string): number {
if (typeof x == 'string') {
if (typeof x === 'string') {
if (x.startsWith('0x')) {
return parseInt(x, 16);
}
Expand Down
Loading

0 comments on commit d20b73d

Please sign in to comment.