diff --git a/examples/onchain-signer/contracts/Gasless.sol b/examples/onchain-signer/contracts/Gasless.sol index c17169872..8e0ea5d78 100644 --- a/examples/onchain-signer/contracts/Gasless.sol +++ b/examples/onchain-signer/contracts/Gasless.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; +import {encryptCallData} from "@oasisprotocol/sapphire-contracts/contracts/CalldataEncryption.sol"; import {EIP155Signer} from "@oasisprotocol/sapphire-contracts/contracts/EIP155Signer.sol"; struct EthereumKeypair { @@ -25,8 +26,27 @@ contract Gasless { bytes memory innercall ) external view returns (bytes memory output) { bytes memory data = abi.encode(innercallAddr, innercall); + return + EIP155Signer.sign( + kp.addr, + kp.secret, + EIP155Signer.EthTx({ + nonce: kp.nonce, + gasPrice: 100_000_000_000, + gasLimit: 250000, + to: address(this), + value: 0, + data: encryptCallData(abi.encodeCall(this.proxy, data)), + chainId: block.chainid + }) + ); + } - // Call will invoke proxy(). + function makeProxyTxPlain( + address innercallAddr, + bytes memory innercall + ) external view returns (bytes memory output) { + bytes memory data = abi.encode(innercallAddr, innercall); return EIP155Signer.sign( kp.addr, diff --git a/examples/onchain-signer/hardhat.config.ts b/examples/onchain-signer/hardhat.config.ts index cdfe50e0e..35e363ff6 100644 --- a/examples/onchain-signer/hardhat.config.ts +++ b/examples/onchain-signer/hardhat.config.ts @@ -26,7 +26,17 @@ const config: HardhatUserConfig = { 'sapphire-testnet': { ...sapphireTestnet, accounts }, 'sapphire-localnet': { ...sapphireLocalnet, accounts }, }, - solidity: '0.8.20', + solidity: { + version: '0.8.20', + settings: { + // XXX: Needs to match https://github.com/oasisprotocol/sapphire-paratime/blob/main/contracts/hardhat.config.ts + optimizer: { + enabled: true, + runs: (1 << 32) - 1, + }, + viaIR: true, + }, + }, }; export default config; diff --git a/examples/onchain-signer/package.json b/examples/onchain-signer/package.json index c2a53a4ee..6a65a43bb 100644 --- a/examples/onchain-signer/package.json +++ b/examples/onchain-signer/package.json @@ -25,6 +25,7 @@ "@nomicfoundation/hardhat-verify": "^2.0.2", "@oasisprotocol/sapphire-contracts": "workspace:^", "@oasisprotocol/sapphire-hardhat": "workspace:^", + "@oasisprotocol/sapphire-paratime": "workspace:^", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@types/mocha": "^9.1.1", diff --git a/examples/onchain-signer/test/CommentBox.ts b/examples/onchain-signer/test/CommentBox.ts index 5699a23ea..2bd784fc0 100644 --- a/examples/onchain-signer/test/CommentBox.ts +++ b/examples/onchain-signer/test/CommentBox.ts @@ -3,6 +3,10 @@ import { ethers } from 'hardhat'; import { parseEther, Wallet } from 'ethers'; import { CommentBox, Gasless } from '../typechain-types'; import { EthereumKeypairStruct } from '../typechain-types/contracts/Gasless'; +import { + isCalldataEnveloped, + wrapEthereumProvider, +} from '@oasisprotocol/sapphire-paratime'; describe('CommentBox', function () { let commentBox: CommentBox; @@ -33,55 +37,85 @@ describe('CommentBox', function () { console.log(' . gasless pubkey', wallet.address); }); - it('Should comment', async function () { - this.timeout(10000); + // Request and send a gasless transaction. Set up sapphire-localnet image to + // run this test: + // docker run -it -p8544-8548:8544-8548 ghcr.io/oasisprotocol/sapphire-localnet + async function commentGasless( + comment: string, + plainProxy: boolean, + unwrappedProvider: boolean, + ) { + const provider = unwrappedProvider + ? ethers.provider + : wrapEthereumProvider(ethers.provider); + + const innercall = commentBox.interface.encodeFunctionData('comment', [ + comment, + ]); + const prevCommentCount = await commentBox.commentCount(); + let tx: string; + if (plainProxy) { + tx = await gasless.makeProxyTxPlain( + await commentBox.getAddress(), + innercall, + ); + } else { + tx = await gasless.makeProxyTx(await commentBox.getAddress(), innercall); + } + + const response = await provider.broadcastTransaction(tx); + expect(isCalldataEnveloped(response.data)).eq(!plainProxy); + await response.wait(); + + const receipt = await provider.getTransactionReceipt(response.hash); + if (!receipt || receipt.status != 1) throw new Error('tx failed'); + + expect(await commentBox.commentCount()).eq(prevCommentCount + BigInt(1)); + } + + it('Should comment', async function () { const prevCommentCount = await commentBox.commentCount(); const tx = await commentBox.comment('Hello, world!'); await tx.wait(); - // Sapphire Mainnet/Testnet: Wait a few moments for nodes to catch up. - const chainId = (await ethers.provider.getNetwork()).chainId; - if (chainId == BigInt(23294) || chainId == BigInt(23295)) { - await new Promise((r) => setTimeout(r, 6_000)); - } - expect(await commentBox.commentCount()).eq(prevCommentCount + BigInt(1)); }); - it('Should comment gasless', async function () { - this.timeout(10000); + it('Should comment gasless (encrypted tx, wrapped client)', async function () { + if ((await ethers.provider.getNetwork()).chainId == BigInt(1337)) { + this.skip(); + } - const provider = ethers.provider; + await commentGasless('Hello, c10l world', false, false); + }); - // You can set up sapphire-localnet image and run the test like this: - // docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-localnet -to 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - // npx hardhat test --grep proxy --network sapphire-localnet - const chainId = (await provider.getNetwork()).chainId; - if (chainId == BigInt(1337)) { + it('Should comment gasless (encrypted tx, unwrapped client)', async function () { + if ((await ethers.provider.getNetwork()).chainId == BigInt(1337)) { this.skip(); } - const innercall = commentBox.interface.encodeFunctionData('comment', [ - 'Hello, free world!', - ]); + await commentGasless('Hello, c10l world', false, true); + }); - // Sapphire Mainnet/Testnet: Wait a few moments for nodes to catch up. - if (chainId == BigInt(23294) || chainId == BigInt(23295)) { - await new Promise((r) => setTimeout(r, 6_000)); + it('Should comment gasless (plain tx, wrapped client)', async function () { + // Set up sapphire-localnet image to run this test: + // docker run -it -p8544-8548:8544-8548 ghcr.io/oasisprotocol/sapphire-localnet + if ((await ethers.provider.getNetwork()).chainId == BigInt(1337)) { + this.skip(); } - const tx = await gasless.makeProxyTx( - await commentBox.getAddress(), - innercall, - ); + await commentGasless('Hello, plain world', true, false); + }); - // TODO: https://github.com/oasisprotocol/sapphire-paratime/issues/179 - const response = await provider.broadcastTransaction(tx); - await response.wait(); + it('Should comment gasless (plain tx, unwrapped client)', async function () { + // Set up sapphire-localnet image to run this test: + // docker run -it -p8544-8548:8544-8548 ghcr.io/oasisprotocol/sapphire-localnet + if ((await ethers.provider.getNetwork()).chainId == BigInt(1337)) { + this.skip(); + } - const receipt = await provider.getTransactionReceipt(response.hash); - if (!receipt || receipt.status != 1) throw new Error('tx failed'); + await commentGasless('Hello, plain world', true, true); }); });