From 937967ae4d0df427a750076cab026bef57ab8390 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Wed, 6 Sep 2023 09:07:18 +0100 Subject: [PATCH 01/20] contracts: submit pre-signed transaction --- contracts/test/eip155.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index c9c325c5..e73ba875 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -3,20 +3,38 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; import { EIP155Tests__factory } from '../typechain-types/factories/contracts/tests'; +import { EIP155Tests } from '../typechain-types/contracts/tests/EIP155Tests'; describe('EIP-155', function () { - async function deploy() { + let testContract : EIP155Tests; + before(async () => { const factory = (await ethers.getContractFactory( 'EIP155Tests', )) as EIP155Tests__factory; - const testContract = await factory.deploy({ + testContract = await factory.deploy({ value: ethers.utils.parseEther('1'), }); - return { testContract }; - } + }); + + it('Encrypts pre-signed transactions', async function () { + const txobj = { + nonce: 0, + gasPrice: await testContract.provider.getGasPrice(), + gasLimit: 250000, + to: testContract.address, + value: 0, + data: '0x', + chainId: 0, + }; + const signedTx = await testContract.sign(txobj); + const response = await testContract.provider.sendTransaction(signedTx); + const receipt = await response.wait(); + expect(receipt.logs[0].data).equal( + '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', + ); + }); it('Signed transactions can be submitted', async function () { - const { testContract } = await deploy(); const txobj = { nonce: 0, gasPrice: await testContract.provider.getGasPrice(), From c1755c8b35ad627e29ea7183dbb902f31aa5ca67 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Sun, 17 Sep 2023 14:40:05 +0200 Subject: [PATCH 02/20] contracts: added test to verify calldata is being encoded --- contracts/contracts/tests/EIP155Tests.sol | 14 +++++++ contracts/package.json | 2 + contracts/test/eip155.ts | 47 +++++++++++++++-------- pnpm-lock.yaml | 6 +++ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/contracts/contracts/tests/EIP155Tests.sol b/contracts/contracts/tests/EIP155Tests.sol index ea4dd777..332c256e 100644 --- a/contracts/contracts/tests/EIP155Tests.sol +++ b/contracts/contracts/tests/EIP155Tests.sol @@ -24,6 +24,20 @@ contract EIP155Tests { return EIP155Signer.sign(publicAddr, secretKey, transaction); } + function signWithSecret( + EIP155Signer.EthTx memory transaction, + address fromPublicAddr, + bytes32 fromSecret + ) + external + view + returns (bytes memory) + { + transaction.data = abi.encodeWithSelector(this.example.selector); + transaction.chainId = block.chainid; + return EIP155Signer.sign(fromPublicAddr, fromSecret, transaction); + } + event ExampleEvent(bytes32 x); function example() external { diff --git a/contracts/package.json b/contracts/package.json index c7f2171e..a82074eb 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -24,6 +24,8 @@ "contracts" ], "devDependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/providers": "^5.7.2", "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", "@nomiclabs/hardhat-ethers": "^2.1.1", "@oasisprotocol/sapphire-hardhat": "workspace:^", diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index e73ba875..54f63e76 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -5,8 +5,30 @@ import { ethers } from 'hardhat'; import { EIP155Tests__factory } from '../typechain-types/factories/contracts/tests'; import { EIP155Tests } from '../typechain-types/contracts/tests/EIP155Tests'; +// Shannon entropy +function entropy(str:string) { + return [...new Set(str)] + .map(chr => { + return str.match(new RegExp(chr, 'g'))!.length; + }) + .reduce((sum, frequency) => { + let p = frequency / str.length; + return sum + p * Math.log2(1 / p); + }, 0); +}; + +/* + * Test cases: + * Sign with different 'from' address + * - This requires a separate provider? + * Sign with same 'from' address + * - This should automagically encrypt + * + * TODO: verify that transaction calldata was submitted encrypted on-chian + */ + describe('EIP-155', function () { - let testContract : EIP155Tests; + let testContract: EIP155Tests; before(async () => { const factory = (await ethers.getContractFactory( 'EIP155Tests', @@ -14,24 +36,14 @@ describe('EIP-155', function () { testContract = await factory.deploy({ value: ethers.utils.parseEther('1'), }); + await testContract.deployed(); }); - it('Encrypts pre-signed transactions', async function () { - const txobj = { - nonce: 0, - gasPrice: await testContract.provider.getGasPrice(), - gasLimit: 250000, - to: testContract.address, - value: 0, - data: '0x', - chainId: 0, - }; - const signedTx = await testContract.sign(txobj); - const response = await testContract.provider.sendTransaction(signedTx); - const receipt = await response.wait(); - expect(receipt.logs[0].data).equal( - '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', - ); + it('Wrapper encrypts transaction calldata', async function () { + const tx = await testContract.example(); + expect(entropy(tx.data)).gte(3.8); + expect(tx.data).not.eq(testContract.interface.encodeFunctionData("example")); + expect(tx.data.length).eq(218); }); it('Signed transactions can be submitted', async function () { @@ -54,6 +66,7 @@ describe('EIP-155', function () { let receipt = await testContract.provider.waitForTransaction( plainResp.hash, ); + expect(plainResp.data).eq(testContract.interface.encodeFunctionData("example")); expect(receipt.logs[0].data).equal( '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51c7a769..431cb226 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,12 @@ importers: contracts: devDependencies: + '@ethersproject/abi': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/providers': + specifier: ^5.7.2 + version: 5.7.2 '@nomicfoundation/hardhat-chai-matchers': specifier: ^1.0.5 version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3)(chai@4.3.7)(ethers@5.7.2)(hardhat@2.16.1) From da94431d3f6b15267797a8ab8002d0d310a0c5d5 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Sun, 17 Sep 2023 14:43:39 +0200 Subject: [PATCH 03/20] contracts: removed notes --- contracts/test/eip155.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 54f63e76..a9d869cf 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -17,16 +17,6 @@ function entropy(str:string) { }, 0); }; -/* - * Test cases: - * Sign with different 'from' address - * - This requires a separate provider? - * Sign with same 'from' address - * - This should automagically encrypt - * - * TODO: verify that transaction calldata was submitted encrypted on-chian - */ - describe('EIP-155', function () { let testContract: EIP155Tests; before(async () => { From 654e0cec3f76de41bdff7dfdc37f182770a884d0 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Sun, 17 Sep 2023 14:47:36 +0200 Subject: [PATCH 04/20] contracts: fixed lint warning --- contracts/contracts/tests/EIP155Tests.sol | 6 +----- contracts/test/eip155.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/contracts/tests/EIP155Tests.sol b/contracts/contracts/tests/EIP155Tests.sol index 332c256e..8294be22 100644 --- a/contracts/contracts/tests/EIP155Tests.sol +++ b/contracts/contracts/tests/EIP155Tests.sol @@ -28,11 +28,7 @@ contract EIP155Tests { EIP155Signer.EthTx memory transaction, address fromPublicAddr, bytes32 fromSecret - ) - external - view - returns (bytes memory) - { + ) external view returns (bytes memory) { transaction.data = abi.encodeWithSelector(this.example.selector); transaction.chainId = block.chainid; return EIP155Signer.sign(fromPublicAddr, fromSecret, transaction); diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index a9d869cf..1ed1e6d5 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -6,16 +6,16 @@ import { EIP155Tests__factory } from '../typechain-types/factories/contracts/tes import { EIP155Tests } from '../typechain-types/contracts/tests/EIP155Tests'; // Shannon entropy -function entropy(str:string) { +function entropy(str: string) { return [...new Set(str)] - .map(chr => { + .map((chr) => { return str.match(new RegExp(chr, 'g'))!.length; }) .reduce((sum, frequency) => { let p = frequency / str.length; return sum + p * Math.log2(1 / p); }, 0); -}; +} describe('EIP-155', function () { let testContract: EIP155Tests; @@ -32,7 +32,9 @@ describe('EIP-155', function () { it('Wrapper encrypts transaction calldata', async function () { const tx = await testContract.example(); expect(entropy(tx.data)).gte(3.8); - expect(tx.data).not.eq(testContract.interface.encodeFunctionData("example")); + expect(tx.data).not.eq( + testContract.interface.encodeFunctionData('example'), + ); expect(tx.data.length).eq(218); }); @@ -56,7 +58,9 @@ describe('EIP-155', function () { let receipt = await testContract.provider.waitForTransaction( plainResp.hash, ); - expect(plainResp.data).eq(testContract.interface.encodeFunctionData("example")); + expect(plainResp.data).eq( + testContract.interface.encodeFunctionData('example'), + ); expect(receipt.logs[0].data).equal( '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', ); From 624656aabc44b18b0795caaf103c1274187c2b4f Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Mon, 18 Sep 2023 01:26:39 +0100 Subject: [PATCH 05/20] contracts: improve EIP-155 test cases --- clients/js/src/compat.ts | 15 ++++-- contracts/package.json | 108 +++++++++++++++++++-------------------- contracts/test/eip155.ts | 54 ++++++++++++++++++-- pnpm-lock.yaml | 6 --- 4 files changed, 114 insertions(+), 69 deletions(-) diff --git a/clients/js/src/compat.ts b/clients/js/src/compat.ts index e7a905d9..bfcdbf0c 100644 --- a/clients/js/src/compat.ts +++ b/clients/js/src/compat.ts @@ -573,11 +573,13 @@ async function repackRawTx( ); } catch (e) { if (e instanceof EnvelopeError) throw e; - if (globalThis?.process?.env?.NODE_ENV !== 'test') { - console.trace(REPACK_ERROR); - } } const tx = ethers6.Transaction.from(raw); + if( tx.isSigned() && (!signer || await signer!.getAddress() != tx.from!) ) { + // us we are be unable to re-sign the encrypted tx, so must passthrough when + // they submit a transaction signed by another keypair + return tx.serialized; + } const q = (v: bigint | null | undefined): string | undefined => { if (!v) return undefined; return ethers6.toQuantity(v); @@ -592,14 +594,17 @@ async function repackRawTx( value: q(tx.value), chainId: Number(tx.chainId), }; - if (!signer) throw new CallError(REPACK_ERROR, null); if (!parsed.gasLimit) parsed.gasLimit = q(BigInt(DEFAULT_GAS)); // TODO(39) try { - return signer.signTransaction({ + return signer!.signTransaction({ ...parsed, data: await cipher.encryptEncode(data), }); } catch (e) { + // Many JSON-RPC providers, Ethers included, will not let you directly + // sign transactions, which is necessary to re-encrypt the calldata! + // Throw an error here to prevent calls which should've been encrypted + // from being submitted unencrypted. throw new CallError(REPACK_ERROR, e); } } diff --git a/contracts/package.json b/contracts/package.json index a82074eb..9fb9d59c 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,57 +1,55 @@ { - "name": "@oasisprotocol/sapphire-contracts", - "version": "0.2.4", - "license": "Apache-2.0", - "description": "Solidity smart contract library for confidential contract development", - "homepage": "https://github.com/oasisprotocol/sapphire-paratime/tree/main/contracts", - "repository": { - "type": "git", - "url": "https://github.com/oasisprotocol/sapphire-paratime.git" - }, - "scripts": { - "lint:eslint": "eslint --ignore-path .gitignore --ext .ts", - "lint:solhint": "solhint 'contracts/**/*.sol'", - "lint::prettier": "prettier --cache --check --plugin-search-dir=. --cache '*.json' '**/*.ts' '**/*.sol'", - "lint": "npm-run-all lint:**", - "format:eslint": "eslint --fix --ignore-path .gitignore --ext .ts", - "format:solhint": "solhint --fix 'contracts/**/*.sol'", - "format::prettier": "prettier --write --plugin-search-dir=. '*.json' '**/*.ts' '**/*.sol'", - "format": "npm-run-all format:**", - "build": "hardhat compile", - "test": "hardhat test" - }, - "files": [ - "contracts" - ], - "devDependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/providers": "^5.7.2", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", - "@nomiclabs/hardhat-ethers": "^2.1.1", - "@oasisprotocol/sapphire-hardhat": "workspace:^", - "@oasisprotocol/client": "^0.1.1-alpha.2", - "@openzeppelin/contracts": "^4.7.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.3", - "@types/chai": "^4.3.3", - "@types/mocha": "^9.1.1", - "@types/node": "^18.7.18", - "@typescript-eslint/eslint-plugin": "^5.37.0", - "@typescript-eslint/parser": "^5.37.0", - "cborg": "^1.9.5", - "chai": "^4.3.6", - "eslint": "^8.23.1", - "eslint-config-prettier": "^8.5.0", - "ethers": "^5.7.1", - "hardhat": "^2.16.1", - "hardhat-watcher": "^2.5.0", - "npm-run-all": "^4.1.5", - "prettier": "^2.7.1", - "prettier-plugin-solidity": "1.0.0-beta.24", - "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.9.1", - "typechain": "^8.1.0", - "typescript": "^4.8.3" + "name": "@oasisprotocol/sapphire-contracts", + "version": "0.2.4", + "license": "Apache-2.0", + "description": "Solidity smart contract library for confidential contract development", + "homepage": "https://github.com/oasisprotocol/sapphire-paratime/tree/main/contracts", + "repository": { + "type": "git", + "url": "https://github.com/oasisprotocol/sapphire-paratime.git" + }, + "scripts": { + "lint:eslint": "eslint --ignore-path .gitignore --ext .ts", + "lint:solhint": "solhint 'contracts/**/*.sol'", + "lint::prettier": "prettier --cache --check --plugin-search-dir=. --cache '*.json' '**/*.ts' '**/*.sol'", + "lint": "npm-run-all lint:**", + "format:eslint": "eslint --fix --ignore-path .gitignore --ext .ts", + "format:solhint": "solhint --fix 'contracts/**/*.sol'", + "format::prettier": "prettier --write --plugin-search-dir=. '*.json' '**/*.ts' '**/*.sol'", + "format": "npm-run-all format:**", + "build": "hardhat compile", + "test": "hardhat test" + }, + "files": [ + "contracts" + ], + "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", + "@nomiclabs/hardhat-ethers": "^2.1.1", + "@oasisprotocol/sapphire-hardhat": "workspace:^", + "@oasisprotocol/client": "^0.1.1-alpha.2", + "@openzeppelin/contracts": "^4.7.3", + "@typechain/ethers-v5": "^10.1.0", + "@typechain/hardhat": "^6.1.3", + "@types/chai": "^4.3.3", + "@types/mocha": "^9.1.1", + "@types/node": "^18.7.18", + "@typescript-eslint/eslint-plugin": "^5.37.0", + "@typescript-eslint/parser": "^5.37.0", + "cborg": "^1.9.5", + "chai": "^4.3.6", + "eslint": "^8.23.1", + "eslint-config-prettier": "^8.5.0", + "ethers": "^5.7.1", + "hardhat": "^2.16.1", + "hardhat-watcher": "^2.5.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.7.1", + "prettier-plugin-solidity": "1.0.0-beta.24", + "solhint": "^3.3.7", + "solidity-coverage": "^0.8.2", + "ts-node": "^10.9.1", + "typechain": "^8.1.0", + "typescript": "^4.8.3" + } } -} diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 1ed1e6d5..c9432579 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; +import * as sapphire from '@oasisprotocol/sapphire-paratime' import { EIP155Tests__factory } from '../typechain-types/factories/contracts/tests'; import { EIP155Tests } from '../typechain-types/contracts/tests/EIP155Tests'; @@ -29,7 +30,7 @@ describe('EIP-155', function () { await testContract.deployed(); }); - it('Wrapper encrypts transaction calldata', async function () { + it('Wrapper encrypts self-signed transaction calldata', async function () { const tx = await testContract.example(); expect(entropy(tx.data)).gte(3.8); expect(tx.data).not.eq( @@ -38,9 +39,9 @@ describe('EIP-155', function () { expect(tx.data.length).eq(218); }); - it('Signed transactions can be submitted', async function () { + it('Other-Signed transaction submission via un-wrapped provider', async function () { const txobj = { - nonce: 0, + nonce: await testContract.provider.getTransactionCount(await testContract.publicAddr()), gasPrice: await testContract.provider.getGasPrice(), gasLimit: 250000, to: testContract.address, @@ -65,4 +66,51 @@ describe('EIP-155', function () { '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', ); }); + + it('Other-Signed transaction submission via wrapped provider', async function () { + const txobj = { + nonce: await testContract.provider.getTransactionCount(await testContract.publicAddr()), + gasPrice: await testContract.provider.getGasPrice(), + gasLimit: 250000, + to: testContract.address, + value: 0, + data: '0x', + chainId: 0, + }; + const signedTx = await testContract.sign(txobj); + + let plainResp = await testContract.signer.provider!.sendTransaction(signedTx); + let receipt = await testContract.provider.waitForTransaction( + plainResp.hash, + ); + expect(plainResp.data).eq( + testContract.interface.encodeFunctionData('example'), + ); + expect(receipt.logs[0].data).equal( + '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', + ); + }); + + it('Self-Signed transaction submission via wrapped provider', async function () { + const p = testContract.provider; + const sk = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; + const wallet = sapphire.wrap(new ethers.Wallet(sk).connect(p)); + const calldata = testContract.interface.encodeFunctionData('example'); + + const signedTx = await wallet.signTransaction({ + gasLimit: 250000, + to: testContract.address, + value: 0, + data: calldata, + chainId: (await p.getNetwork()).chainId, + gasPrice: (await p.getGasPrice()), + nonce: await p.getTransactionCount(wallet.address) + }); + + let x = await testContract.signer.provider!.sendTransaction(signedTx); + let r = await testContract.provider.waitForTransaction(x.hash); + + expect(x.data).not.eq(calldata); + expect(r.logs[0].data).equal('0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 431cb226..51c7a769 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,12 +117,6 @@ importers: contracts: devDependencies: - '@ethersproject/abi': - specifier: ^5.7.0 - version: 5.7.0 - '@ethersproject/providers': - specifier: ^5.7.2 - version: 5.7.2 '@nomicfoundation/hardhat-chai-matchers': specifier: ^1.0.5 version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3)(chai@4.3.7)(ethers@5.7.2)(hardhat@2.16.1) From 840aec00b1ae1df744c981a9a680b2976f3bfb93 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Mon, 18 Sep 2023 01:45:02 +0100 Subject: [PATCH 06/20] contracts: cleaned-up EIP-155 tests --- contracts/test/eip155.ts | 57 +++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index c9432579..c48eac52 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -2,10 +2,13 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; -import * as sapphire from '@oasisprotocol/sapphire-paratime' +import * as sapphire from '@oasisprotocol/sapphire-paratime'; import { EIP155Tests__factory } from '../typechain-types/factories/contracts/tests'; import { EIP155Tests } from '../typechain-types/contracts/tests/EIP155Tests'; +const EXPECTED_EVENT = + '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'; + // Shannon entropy function entropy(str: string) { return [...new Set(str)] @@ -40,16 +43,18 @@ describe('EIP-155', function () { }); it('Other-Signed transaction submission via un-wrapped provider', async function () { - const txobj = { - nonce: await testContract.provider.getTransactionCount(await testContract.publicAddr()), - gasPrice: await testContract.provider.getGasPrice(), + const provider = testContract.provider; + const signedTx = await testContract.sign({ + nonce: await provider.getTransactionCount( + await testContract.publicAddr(), + ), + gasPrice: await provider.getGasPrice(), gasLimit: 250000, to: testContract.address, value: 0, data: '0x', chainId: 0, - }; - const signedTx = await testContract.sign(txobj); + }); // Submit signed transaction via plain JSON-RPC provider (avoiding saphire.wrap) const plainProvider = new ethers.providers.StaticJsonRpcProvider( @@ -62,39 +67,37 @@ describe('EIP-155', function () { expect(plainResp.data).eq( testContract.interface.encodeFunctionData('example'), ); - expect(receipt.logs[0].data).equal( - '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', - ); + expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); it('Other-Signed transaction submission via wrapped provider', async function () { - const txobj = { - nonce: await testContract.provider.getTransactionCount(await testContract.publicAddr()), + const signedTx = await testContract.sign({ + nonce: await testContract.provider.getTransactionCount( + await testContract.publicAddr(), + ), gasPrice: await testContract.provider.getGasPrice(), gasLimit: 250000, to: testContract.address, value: 0, data: '0x', chainId: 0, - }; - const signedTx = await testContract.sign(txobj); + }); - let plainResp = await testContract.signer.provider!.sendTransaction(signedTx); + let plainResp = await testContract.provider.sendTransaction(signedTx); let receipt = await testContract.provider.waitForTransaction( plainResp.hash, ); expect(plainResp.data).eq( testContract.interface.encodeFunctionData('example'), ); - expect(receipt.logs[0].data).equal( - '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', - ); + expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); it('Self-Signed transaction submission via wrapped provider', async function () { - const p = testContract.provider; - const sk = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; - const wallet = sapphire.wrap(new ethers.Wallet(sk).connect(p)); + const provider = testContract.provider; + const sk = + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; + const wallet = sapphire.wrap(new ethers.Wallet(sk).connect(provider)); const calldata = testContract.interface.encodeFunctionData('example'); const signedTx = await wallet.signTransaction({ @@ -102,15 +105,15 @@ describe('EIP-155', function () { to: testContract.address, value: 0, data: calldata, - chainId: (await p.getNetwork()).chainId, - gasPrice: (await p.getGasPrice()), - nonce: await p.getTransactionCount(wallet.address) + chainId: (await provider.getNetwork()).chainId, + gasPrice: await provider.getGasPrice(), + nonce: await provider.getTransactionCount(wallet.address), }); - let x = await testContract.signer.provider!.sendTransaction(signedTx); - let r = await testContract.provider.waitForTransaction(x.hash); - + let x = await provider.sendTransaction(signedTx); expect(x.data).not.eq(calldata); - expect(r.logs[0].data).equal('0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'); + + let r = await provider.waitForTransaction(x.hash); + expect(r.logs[0].data).equal(EXPECTED_EVENT); }); }); From 88cf4af2e6876ebd9473224de555ecc004791d06 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:36:05 +0100 Subject: [PATCH 07/20] contracts: eip-155: removed dependence on hard-coded secret --- contracts/test/eip155.ts | 48 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index c48eac52..6454ae61 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -1,10 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; -import { ethers } from 'hardhat'; +import hre, { ethers } from 'hardhat'; import * as sapphire from '@oasisprotocol/sapphire-paratime'; import { EIP155Tests__factory } from '../typechain-types/factories/contracts/tests'; import { EIP155Tests } from '../typechain-types/contracts/tests/EIP155Tests'; +import { HardhatNetworkHDAccountsConfig } from 'hardhat/types'; const EXPECTED_EVENT = '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'; @@ -21,8 +22,18 @@ function entropy(str: string) { }, 0); } +function getWallet(index: number) { + const accounts = hre.network.config + .accounts as HardhatNetworkHDAccountsConfig; + return ethers.Wallet.fromMnemonic( + accounts.mnemonic, + accounts.path + `/${index}`, + ); +} + describe('EIP-155', function () { let testContract: EIP155Tests; + let calldata: string; before(async () => { const factory = (await ethers.getContractFactory( 'EIP155Tests', @@ -31,14 +42,13 @@ describe('EIP-155', function () { value: ethers.utils.parseEther('1'), }); await testContract.deployed(); + calldata = testContract.interface.encodeFunctionData('example'); }); it('Wrapper encrypts self-signed transaction calldata', async function () { const tx = await testContract.example(); expect(entropy(tx.data)).gte(3.8); - expect(tx.data).not.eq( - testContract.interface.encodeFunctionData('example'), - ); + expect(tx.data).not.eq(calldata); expect(tx.data.length).eq(218); }); @@ -64,18 +74,17 @@ describe('EIP-155', function () { let receipt = await testContract.provider.waitForTransaction( plainResp.hash, ); - expect(plainResp.data).eq( - testContract.interface.encodeFunctionData('example'), - ); + expect(plainResp.data).eq(calldata); expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); it('Other-Signed transaction submission via wrapped provider', async function () { + const provider = testContract.provider; const signedTx = await testContract.sign({ - nonce: await testContract.provider.getTransactionCount( + nonce: await provider.getTransactionCount( await testContract.publicAddr(), ), - gasPrice: await testContract.provider.getGasPrice(), + gasPrice: await provider.getGasPrice(), gasLimit: 250000, to: testContract.address, value: 0, @@ -83,22 +92,15 @@ describe('EIP-155', function () { chainId: 0, }); - let plainResp = await testContract.provider.sendTransaction(signedTx); - let receipt = await testContract.provider.waitForTransaction( - plainResp.hash, - ); - expect(plainResp.data).eq( - testContract.interface.encodeFunctionData('example'), - ); + let plainResp = await provider.sendTransaction(signedTx); + let receipt = await provider.waitForTransaction(plainResp.hash); + expect(plainResp.data).eq(calldata); expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); - it('Self-Signed transaction submission via wrapped provider', async function () { + it('Self-Signed transaction submission via wrapped wallet', async function () { const provider = testContract.provider; - const sk = - '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; - const wallet = sapphire.wrap(new ethers.Wallet(sk).connect(provider)); - const calldata = testContract.interface.encodeFunctionData('example'); + const wallet = sapphire.wrap(getWallet(0).connect(provider)); const signedTx = await wallet.signTransaction({ gasLimit: 250000, @@ -110,10 +112,14 @@ describe('EIP-155', function () { nonce: await provider.getTransactionCount(wallet.address), }); + // Calldata should be encrypted when we wrap the wallet provider let x = await provider.sendTransaction(signedTx); + expect(entropy(x.data)).gte(3.8); expect(x.data).not.eq(calldata); let r = await provider.waitForTransaction(x.hash); expect(r.logs[0].data).equal(EXPECTED_EVENT); }); + + // TODO: test error conditions? }); From 6a0258ab5786ebecfa151e81ed9d46d24828ec62 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:38:07 +0100 Subject: [PATCH 08/20] clients: fixed formatting in compat.ts --- clients/js/src/compat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/src/compat.ts b/clients/js/src/compat.ts index bfcdbf0c..4d4817ca 100644 --- a/clients/js/src/compat.ts +++ b/clients/js/src/compat.ts @@ -575,7 +575,7 @@ async function repackRawTx( if (e instanceof EnvelopeError) throw e; } const tx = ethers6.Transaction.from(raw); - if( tx.isSigned() && (!signer || await signer!.getAddress() != tx.from!) ) { + if (tx.isSigned() && (!signer || (await signer!.getAddress()) != tx.from!)) { // us we are be unable to re-sign the encrypted tx, so must passthrough when // they submit a transaction signed by another keypair return tx.serialized; From 563ce410fd425c0404396b83d610db2a13af42a8 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:00:12 +0100 Subject: [PATCH 09/20] Update clients/js/src/compat.ts Co-authored-by: Xi Zhang --- clients/js/src/compat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/src/compat.ts b/clients/js/src/compat.ts index 4d4817ca..6bf3f55c 100644 --- a/clients/js/src/compat.ts +++ b/clients/js/src/compat.ts @@ -576,7 +576,7 @@ async function repackRawTx( } const tx = ethers6.Transaction.from(raw); if (tx.isSigned() && (!signer || (await signer!.getAddress()) != tx.from!)) { - // us we are be unable to re-sign the encrypted tx, so must passthrough when + // we are be unable to re-sign the encrypted tx, so must passthrough when // they submit a transaction signed by another keypair return tx.serialized; } From 59030493bd6d0c3878a3ba8a85d4550a24ea4129 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:30:14 +0100 Subject: [PATCH 10/20] contracts: format updates package.json --- contracts/package.json | 106 ++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index 9fb9d59c..c7f2171e 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,55 +1,55 @@ { - "name": "@oasisprotocol/sapphire-contracts", - "version": "0.2.4", - "license": "Apache-2.0", - "description": "Solidity smart contract library for confidential contract development", - "homepage": "https://github.com/oasisprotocol/sapphire-paratime/tree/main/contracts", - "repository": { - "type": "git", - "url": "https://github.com/oasisprotocol/sapphire-paratime.git" - }, - "scripts": { - "lint:eslint": "eslint --ignore-path .gitignore --ext .ts", - "lint:solhint": "solhint 'contracts/**/*.sol'", - "lint::prettier": "prettier --cache --check --plugin-search-dir=. --cache '*.json' '**/*.ts' '**/*.sol'", - "lint": "npm-run-all lint:**", - "format:eslint": "eslint --fix --ignore-path .gitignore --ext .ts", - "format:solhint": "solhint --fix 'contracts/**/*.sol'", - "format::prettier": "prettier --write --plugin-search-dir=. '*.json' '**/*.ts' '**/*.sol'", - "format": "npm-run-all format:**", - "build": "hardhat compile", - "test": "hardhat test" - }, - "files": [ - "contracts" - ], - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", - "@nomiclabs/hardhat-ethers": "^2.1.1", - "@oasisprotocol/sapphire-hardhat": "workspace:^", - "@oasisprotocol/client": "^0.1.1-alpha.2", - "@openzeppelin/contracts": "^4.7.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.3", - "@types/chai": "^4.3.3", - "@types/mocha": "^9.1.1", - "@types/node": "^18.7.18", - "@typescript-eslint/eslint-plugin": "^5.37.0", - "@typescript-eslint/parser": "^5.37.0", - "cborg": "^1.9.5", - "chai": "^4.3.6", - "eslint": "^8.23.1", - "eslint-config-prettier": "^8.5.0", - "ethers": "^5.7.1", - "hardhat": "^2.16.1", - "hardhat-watcher": "^2.5.0", - "npm-run-all": "^4.1.5", - "prettier": "^2.7.1", - "prettier-plugin-solidity": "1.0.0-beta.24", - "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.9.1", - "typechain": "^8.1.0", - "typescript": "^4.8.3" - } + "name": "@oasisprotocol/sapphire-contracts", + "version": "0.2.4", + "license": "Apache-2.0", + "description": "Solidity smart contract library for confidential contract development", + "homepage": "https://github.com/oasisprotocol/sapphire-paratime/tree/main/contracts", + "repository": { + "type": "git", + "url": "https://github.com/oasisprotocol/sapphire-paratime.git" + }, + "scripts": { + "lint:eslint": "eslint --ignore-path .gitignore --ext .ts", + "lint:solhint": "solhint 'contracts/**/*.sol'", + "lint::prettier": "prettier --cache --check --plugin-search-dir=. --cache '*.json' '**/*.ts' '**/*.sol'", + "lint": "npm-run-all lint:**", + "format:eslint": "eslint --fix --ignore-path .gitignore --ext .ts", + "format:solhint": "solhint --fix 'contracts/**/*.sol'", + "format::prettier": "prettier --write --plugin-search-dir=. '*.json' '**/*.ts' '**/*.sol'", + "format": "npm-run-all format:**", + "build": "hardhat compile", + "test": "hardhat test" + }, + "files": [ + "contracts" + ], + "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", + "@nomiclabs/hardhat-ethers": "^2.1.1", + "@oasisprotocol/sapphire-hardhat": "workspace:^", + "@oasisprotocol/client": "^0.1.1-alpha.2", + "@openzeppelin/contracts": "^4.7.3", + "@typechain/ethers-v5": "^10.1.0", + "@typechain/hardhat": "^6.1.3", + "@types/chai": "^4.3.3", + "@types/mocha": "^9.1.1", + "@types/node": "^18.7.18", + "@typescript-eslint/eslint-plugin": "^5.37.0", + "@typescript-eslint/parser": "^5.37.0", + "cborg": "^1.9.5", + "chai": "^4.3.6", + "eslint": "^8.23.1", + "eslint-config-prettier": "^8.5.0", + "ethers": "^5.7.1", + "hardhat": "^2.16.1", + "hardhat-watcher": "^2.5.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.7.1", + "prettier-plugin-solidity": "1.0.0-beta.24", + "solhint": "^3.3.7", + "solidity-coverage": "^0.8.2", + "ts-node": "^10.9.1", + "typechain": "^8.1.0", + "typescript": "^4.8.3" } +} From 0010a28d951b64389937996e7a5909bbf5374496 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:48:41 +0100 Subject: [PATCH 11/20] contracts: fix package.json & notes in eip155.ts --- contracts/package.json | 1 + contracts/test/eip155.ts | 12 ++++++++---- pnpm-lock.yaml | 3 +++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index c7f2171e..74581594 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -26,6 +26,7 @@ "devDependencies": { "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", "@nomiclabs/hardhat-ethers": "^2.1.1", + "@oasisprotocol/sapphire-paratime": "workspace:^", "@oasisprotocol/sapphire-hardhat": "workspace:^", "@oasisprotocol/client": "^0.1.1-alpha.2", "@openzeppelin/contracts": "^4.7.3", diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 6454ae61..87f6de8d 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -10,6 +10,8 @@ import { HardhatNetworkHDAccountsConfig } from 'hardhat/types'; const EXPECTED_EVENT = '0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'; +const EXPECTED_ENTROPY_ENCRYPTED = 3.8; + // Shannon entropy function entropy(str: string) { return [...new Set(str)] @@ -47,11 +49,12 @@ describe('EIP-155', function () { it('Wrapper encrypts self-signed transaction calldata', async function () { const tx = await testContract.example(); - expect(entropy(tx.data)).gte(3.8); + expect(entropy(tx.data)).gte(EXPECTED_ENTROPY_ENCRYPTED); expect(tx.data).not.eq(calldata); expect(tx.data.length).eq(218); }); + /// Verifies that contract can sign a transaction, and we submit it via unwrapped provider it('Other-Signed transaction submission via un-wrapped provider', async function () { const provider = testContract.provider; const signedTx = await testContract.sign({ @@ -78,6 +81,8 @@ describe('EIP-155', function () { expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); + /// Verifies that a contract can sign a transaction, and we can submit it via a wrapped provider + /// This lets transactions signed by other accounts pass-through the wrapped provider it('Other-Signed transaction submission via wrapped provider', async function () { const provider = testContract.provider; const signedTx = await testContract.sign({ @@ -98,6 +103,7 @@ describe('EIP-155', function () { expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); + /// Verifies that submitting a manually signed transaction will be encrypted it('Self-Signed transaction submission via wrapped wallet', async function () { const provider = testContract.provider; const wallet = sapphire.wrap(getWallet(0).connect(provider)); @@ -114,12 +120,10 @@ describe('EIP-155', function () { // Calldata should be encrypted when we wrap the wallet provider let x = await provider.sendTransaction(signedTx); - expect(entropy(x.data)).gte(3.8); + expect(entropy(x.data)).gte(EXPECTED_ENTROPY_ENCRYPTED); expect(x.data).not.eq(calldata); let r = await provider.waitForTransaction(x.hash); expect(r.logs[0].data).equal(EXPECTED_EVENT); }); - - // TODO: test error conditions? }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51c7a769..4e065bef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,9 @@ importers: '@oasisprotocol/sapphire-hardhat': specifier: workspace:^ version: link:../integrations/hardhat + '@oasisprotocol/sapphire-paratime': + specifier: workspace:^ + version: link:../clients/js '@openzeppelin/contracts': specifier: ^4.7.3 version: 4.9.2 From 1863d37994b610a8e7acc1117450abc8e95f9272 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:11:55 +0100 Subject: [PATCH 12/20] Update contracts/test/eip155.ts Co-authored-by: Xi Zhang --- contracts/test/eip155.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 87f6de8d..618168dd 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -103,7 +103,7 @@ describe('EIP-155', function () { expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); - /// Verifies that submitting a manually signed transaction will be encrypted + /// Verify that the wrapped wallet will encrypt a manually signed transaction it('Self-Signed transaction submission via wrapped wallet', async function () { const provider = testContract.provider; const wallet = sapphire.wrap(getWallet(0).connect(provider)); From e2f6bfb0176eaf36eab2653872b5e80233e15443 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:12:04 +0100 Subject: [PATCH 13/20] Update contracts/test/eip155.ts Co-authored-by: Xi Zhang --- contracts/test/eip155.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 618168dd..5d6eba6f 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -69,7 +69,7 @@ describe('EIP-155', function () { chainId: 0, }); - // Submit signed transaction via plain JSON-RPC provider (avoiding saphire.wrap) + // Submit signed transaction via plain JSON-RPC provider (avoiding sapphire.wrap) const plainProvider = new ethers.providers.StaticJsonRpcProvider( ethers.provider.connection, ); From b1db643b085a3571b20346aab74d7393420d1f3f Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:12:12 +0100 Subject: [PATCH 14/20] Update clients/js/src/compat.ts Co-authored-by: Xi Zhang --- clients/js/src/compat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/js/src/compat.ts b/clients/js/src/compat.ts index 6bf3f55c..75785911 100644 --- a/clients/js/src/compat.ts +++ b/clients/js/src/compat.ts @@ -576,8 +576,8 @@ async function repackRawTx( } const tx = ethers6.Transaction.from(raw); if (tx.isSigned() && (!signer || (await signer!.getAddress()) != tx.from!)) { - // we are be unable to re-sign the encrypted tx, so must passthrough when - // they submit a transaction signed by another keypair + // encrypted tx cannot be re-signed, allow passthrough when + // submitting a transaction signed by another keypair return tx.serialized; } const q = (v: bigint | null | undefined): string | undefined => { From dce920d7921ab5fcd0c31c52f5bfb54d11ff9f5e Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:12:20 +0100 Subject: [PATCH 15/20] Update contracts/test/eip155.ts Co-authored-by: Xi Zhang --- contracts/test/eip155.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 5d6eba6f..39d85c16 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -54,7 +54,7 @@ describe('EIP-155', function () { expect(tx.data.length).eq(218); }); - /// Verifies that contract can sign a transaction, and we submit it via unwrapped provider + /// Verify that contracts can sign transactions for submission with an unwrapped provider it('Other-Signed transaction submission via un-wrapped provider', async function () { const provider = testContract.provider; const signedTx = await testContract.sign({ From 2f24a44d475aee6304e1b17a6a65087b5ede0ee9 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:12:28 +0100 Subject: [PATCH 16/20] Update contracts/test/eip155.ts Co-authored-by: Xi Zhang --- contracts/test/eip155.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 39d85c16..68237d51 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -81,7 +81,7 @@ describe('EIP-155', function () { expect(receipt.logs[0].data).equal(EXPECTED_EVENT); }); - /// Verifies that a contract can sign a transaction, and we can submit it via a wrapped provider + /// Verify that contracts can sign transactions for submission with a wrapped provider /// This lets transactions signed by other accounts pass-through the wrapped provider it('Other-Signed transaction submission via wrapped provider', async function () { const provider = testContract.provider; From 3cd98646cf16dd660e51f2198471357c3d160abd Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:12:36 +0100 Subject: [PATCH 17/20] Update contracts/test/eip155.ts Co-authored-by: Xi Zhang --- contracts/test/eip155.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 68237d51..46c1cea6 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -82,7 +82,7 @@ describe('EIP-155', function () { }); /// Verify that contracts can sign transactions for submission with a wrapped provider - /// This lets transactions signed by other accounts pass-through the wrapped provider + /// Transactions signed by other accounts should pass-through the wrapped provider it('Other-Signed transaction submission via wrapped provider', async function () { const provider = testContract.provider; const signedTx = await testContract.sign({ From da50e0c2f719ebb351e760507cdf3678e9130b0e Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:31:22 +0100 Subject: [PATCH 18/20] contracts: push changes for matevz to check --- contracts/contracts/tests/EIP155Tests.sol | 11 +++ contracts/hardhat.config.ts | 2 +- contracts/test/eip155.ts | 14 ++++ .../onchain-signer/contracts/CommentBox.sol | 4 + examples/onchain-signer/test/CommentBox.ts | 76 +++++++++---------- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/contracts/contracts/tests/EIP155Tests.sol b/contracts/contracts/tests/EIP155Tests.sol index 8294be22..3aebc462 100644 --- a/contracts/contracts/tests/EIP155Tests.sol +++ b/contracts/contracts/tests/EIP155Tests.sol @@ -14,6 +14,17 @@ contract EIP155Tests { payable(publicAddr).transfer(msg.value); } + function getChainId () external view returns (uint) + { + return block.chainid; + } + + event HasChainId (uint); + + function emitChainId () external { + emit HasChainId(block.chainid); + } + function sign(EIP155Signer.EthTx memory transaction) external view diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index f3dc757c..82a39810 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -64,7 +64,7 @@ const config: HardhatUserConfig = { chainId: 0x5aff, accounts: process.env.SAPPHIRE_TESTNET_PRIVATE_KEY ? [process.env.SAPPHIRE_TESTNET_PRIVATE_KEY] - : [], + : TEST_HDWALLET, }, 'sapphire-mainnet': { url: 'https://sapphire.oasis.io', diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 46c1cea6..8444e151 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -47,6 +47,20 @@ describe('EIP-155', function () { calldata = testContract.interface.encodeFunctionData('example'); }); + it('Has correct block.chainid', async () => { + const provider = testContract.provider; + const expectedChainId = (await provider.getNetwork()).chainId; + + // Emitted via transaction + const tx = await testContract.emitChainId(); + const receipt = await tx.wait(); + expect(receipt.events![0].args![0]).eq(expectedChainId); + + // Returned from view call + const onchainChainId = await testContract.getChainId(); + expect(onchainChainId).eq(expectedChainId); + }); + it('Wrapper encrypts self-signed transaction calldata', async function () { const tx = await testContract.example(); expect(entropy(tx.data)).gte(EXPECTED_ENTROPY_ENCRYPTED); diff --git a/examples/onchain-signer/contracts/CommentBox.sol b/examples/onchain-signer/contracts/CommentBox.sol index 8d38189a..2f1f5356 100644 --- a/examples/onchain-signer/contracts/CommentBox.sol +++ b/examples/onchain-signer/contracts/CommentBox.sol @@ -4,6 +4,10 @@ pragma solidity ^0.8.19; contract CommentBox { string[] public comments; + function commentCount() external view returns (uint) { + return comments.length; + } + function comment(string memory commentText) external { comments.push(commentText); } diff --git a/examples/onchain-signer/test/CommentBox.ts b/examples/onchain-signer/test/CommentBox.ts index 8a4f960f..5efedb56 100644 --- a/examples/onchain-signer/test/CommentBox.ts +++ b/examples/onchain-signer/test/CommentBox.ts @@ -1,21 +1,27 @@ import { expect } from 'chai'; import { config, ethers } from 'hardhat'; +import { CommentBox, Gasless } from '../typechain-types'; +import { HDAccountsUserConfig } from 'hardhat/types'; describe('CommentBox', function () { - async function deployCommentBoxWithProxy() { - const CommentBox = await ethers.getContractFactory('CommentBox'); - const commentBox = await CommentBox.deploy(); + let commentBox : CommentBox; + let gasless : Gasless; - const Gasless = await ethers.getContractFactory('Gasless'); - const gasless = await Gasless.deploy(); + before(async () => { + const CommentBoxFactory = await ethers.getContractFactory('CommentBox'); + commentBox = await CommentBoxFactory.deploy(); + await commentBox.deployed(); + + const GaslessFactory = await ethers.getContractFactory('Gasless'); + gasless = await GaslessFactory.deploy(); + await gasless.deployed(); // Derive the private key of the 1st (counting from 0) builtin hardhat test account. - const accounts = config.networks.hardhat.accounts; + const accounts = (config.networks.hardhat.accounts as unknown) as HDAccountsUserConfig; const wallet1 = ethers.Wallet.fromMnemonic( accounts.mnemonic, accounts.path + `/1`, ); - const privateKey1 = wallet1.privateKey; // Use it as the relayer private key. await expect( @@ -27,41 +33,35 @@ describe('CommentBox', function () { nonce: ethers.provider.getTransactionCount(wallet1.address), }), ).not.to.be.reverted; + }); - return { commentBox, gasless }; - } - - describe('Deployment', function () { - it('Should comment', async function () { - const { commentBox, _gasless } = await deployCommentBoxWithProxy(); - - await expect(commentBox.comment('Hello, world!')).not.to.be.reverted; - expect(commentBox.comments()).length == 1; - }); + it('Should comment', async function () { + const prevCommentCount = await commentBox.commentCount(); - it('Should comment gasless', async function () { - // This test requires RNG and runs on the Sapphire network only. - // You can set up sapphire-dev image and run the test like this: - // docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-dev -to 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - // npx hardhat test --grep proxy --network sapphire-localnet - if ((await ethers.provider.getNetwork()).chainId == 1337) { - this.skip(); - } - const { commentBox, gasless } = await deployCommentBoxWithProxy(); + await expect(commentBox.comment('Hello, world!')).not.to.be.reverted; + expect(await commentBox.commentCount()).eq(prevCommentCount.add(1)); + }); - const innercall = commentBox.interface.encodeFunctionData('comment', [ - 'Hello, free world!', - ]); - const tx = await gasless.makeProxyTx(commentBox.address, innercall); + it('Should comment gasless', async function () { + // This test requires RNG and runs on the Sapphire network only. + // You can set up sapphire-dev image and run the test like this: + // docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-dev -to 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + // npx hardhat test --grep proxy --network sapphire-localnet + if ((await ethers.provider.getNetwork()).chainId == 1337) { + this.skip(); + } + const innercall = commentBox.interface.encodeFunctionData('comment', [ + 'Hello, free world!', + ]); + const tx = await gasless.makeProxyTx(commentBox.address, innercall); - // TODO: https://github.com/oasisprotocol/sapphire-paratime/issues/179 - const plainProvider = new ethers.providers.JsonRpcProvider( - ethers.provider.connection, - ); - const plainResp = await plainProvider.sendTransaction(tx); + // TODO: https://github.com/oasisprotocol/sapphire-paratime/issues/179 + const plainProvider = new ethers.providers.JsonRpcProvider( + ethers.provider.connection, + ); + const plainResp = await plainProvider.sendTransaction(tx); - const receipt = await ethers.provider.waitForTransaction(plainResp.hash); - if (!receipt || receipt.status != 1) throw new Error('tx failed'); - }); + const receipt = await ethers.provider.waitForTransaction(plainResp.hash); + if (!receipt || receipt.status != 1) throw new Error('tx failed'); }); }); From d9d62eb08e2522c1d368492b448c9e0917ddd1d5 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:24:01 +0100 Subject: [PATCH 19/20] contracts: updated EIP155 tests & onchain-signer example --- contracts/hardhat.config.ts | 2 +- contracts/test/eip155.ts | 8 ++++---- examples/onchain-signer/test/CommentBox.ts | 13 ++++--------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 82a39810..67427a34 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -100,7 +100,7 @@ const config: HardhatUserConfig = { }, mocha: { require: ['ts-node/register/files'], - timeout: 20_000, + timeout: 60_000, }, }; diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 8444e151..2596b587 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -27,6 +27,9 @@ function entropy(str: string) { function getWallet(index: number) { const accounts = hre.network.config .accounts as HardhatNetworkHDAccountsConfig; + if( ! accounts.mnemonic ) { + return new ethers.Wallet((accounts as unknown as string[])[0]); + } return ethers.Wallet.fromMnemonic( accounts.mnemonic, accounts.path + `/${index}`, @@ -84,10 +87,7 @@ describe('EIP-155', function () { }); // Submit signed transaction via plain JSON-RPC provider (avoiding sapphire.wrap) - const plainProvider = new ethers.providers.StaticJsonRpcProvider( - ethers.provider.connection, - ); - let plainResp = await plainProvider.sendTransaction(signedTx); + let plainResp = await provider.sendTransaction(signedTx); let receipt = await testContract.provider.waitForTransaction( plainResp.hash, ); diff --git a/examples/onchain-signer/test/CommentBox.ts b/examples/onchain-signer/test/CommentBox.ts index 5efedb56..48d08b96 100644 --- a/examples/onchain-signer/test/CommentBox.ts +++ b/examples/onchain-signer/test/CommentBox.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { config, ethers } from 'hardhat'; +import hre, { config, ethers } from 'hardhat'; import { CommentBox, Gasless } from '../typechain-types'; import { HDAccountsUserConfig } from 'hardhat/types'; @@ -38,12 +38,12 @@ describe('CommentBox', function () { it('Should comment', async function () { const prevCommentCount = await commentBox.commentCount(); - await expect(commentBox.comment('Hello, world!')).not.to.be.reverted; + const tx = await commentBox.comment('Hello, world!'); + await tx.wait(); expect(await commentBox.commentCount()).eq(prevCommentCount.add(1)); }); it('Should comment gasless', async function () { - // This test requires RNG and runs on the Sapphire network only. // You can set up sapphire-dev image and run the test like this: // docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-dev -to 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 // npx hardhat test --grep proxy --network sapphire-localnet @@ -55,12 +55,7 @@ describe('CommentBox', function () { ]); const tx = await gasless.makeProxyTx(commentBox.address, innercall); - // TODO: https://github.com/oasisprotocol/sapphire-paratime/issues/179 - const plainProvider = new ethers.providers.JsonRpcProvider( - ethers.provider.connection, - ); - const plainResp = await plainProvider.sendTransaction(tx); - + const plainResp = await gasless.provider.sendTransaction(tx); const receipt = await ethers.provider.waitForTransaction(plainResp.hash); if (!receipt || receipt.status != 1) throw new Error('tx failed'); }); From 988e06f1f907075897ce160225376228ac10a3d7 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:32:40 +0100 Subject: [PATCH 20/20] contracts: formatting --- contracts/contracts/tests/EIP155Tests.sol | 7 +++---- contracts/test/eip155.ts | 2 +- examples/onchain-signer/contracts/CommentBox.sol | 2 +- examples/onchain-signer/test/CommentBox.ts | 7 ++++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/contracts/tests/EIP155Tests.sol b/contracts/contracts/tests/EIP155Tests.sol index 3aebc462..7fad44f7 100644 --- a/contracts/contracts/tests/EIP155Tests.sol +++ b/contracts/contracts/tests/EIP155Tests.sol @@ -14,14 +14,13 @@ contract EIP155Tests { payable(publicAddr).transfer(msg.value); } - function getChainId () external view returns (uint) - { + function getChainId() external view returns (uint256) { return block.chainid; } - event HasChainId (uint); + event HasChainId(uint256); - function emitChainId () external { + function emitChainId() external { emit HasChainId(block.chainid); } diff --git a/contracts/test/eip155.ts b/contracts/test/eip155.ts index 2596b587..fd31b006 100644 --- a/contracts/test/eip155.ts +++ b/contracts/test/eip155.ts @@ -27,7 +27,7 @@ function entropy(str: string) { function getWallet(index: number) { const accounts = hre.network.config .accounts as HardhatNetworkHDAccountsConfig; - if( ! accounts.mnemonic ) { + if (!accounts.mnemonic) { return new ethers.Wallet((accounts as unknown as string[])[0]); } return ethers.Wallet.fromMnemonic( diff --git a/examples/onchain-signer/contracts/CommentBox.sol b/examples/onchain-signer/contracts/CommentBox.sol index 2f1f5356..82c84b16 100644 --- a/examples/onchain-signer/contracts/CommentBox.sol +++ b/examples/onchain-signer/contracts/CommentBox.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; contract CommentBox { string[] public comments; - function commentCount() external view returns (uint) { + function commentCount() external view returns (uint256) { return comments.length; } diff --git a/examples/onchain-signer/test/CommentBox.ts b/examples/onchain-signer/test/CommentBox.ts index 48d08b96..b7b64ee2 100644 --- a/examples/onchain-signer/test/CommentBox.ts +++ b/examples/onchain-signer/test/CommentBox.ts @@ -4,8 +4,8 @@ import { CommentBox, Gasless } from '../typechain-types'; import { HDAccountsUserConfig } from 'hardhat/types'; describe('CommentBox', function () { - let commentBox : CommentBox; - let gasless : Gasless; + let commentBox: CommentBox; + let gasless: Gasless; before(async () => { const CommentBoxFactory = await ethers.getContractFactory('CommentBox'); @@ -17,7 +17,8 @@ describe('CommentBox', function () { await gasless.deployed(); // Derive the private key of the 1st (counting from 0) builtin hardhat test account. - const accounts = (config.networks.hardhat.accounts as unknown) as HDAccountsUserConfig; + const accounts = config.networks.hardhat + .accounts as unknown as HDAccountsUserConfig; const wallet1 = ethers.Wallet.fromMnemonic( accounts.mnemonic, accounts.path + `/1`,