Merge pull request #229 from getwax/wax-215-add-compression

Add compression to bundler
This commit is contained in:
Jake C-T
2024-06-18 19:45:34 -06:00
committed by GitHub
14 changed files with 295 additions and 117 deletions

View File

@@ -7,6 +7,11 @@ type ConfigType = {
addFundsEthAmount?: string;
deployerSeedPhrase: string;
requirePermission?: boolean;
externalContracts?: {
entryPoint: string;
blsSignatureAggregator: string;
addressRegistry: string;
};
};
export default ConfigType;

View File

@@ -15,11 +15,24 @@ const config: ConfigType = {
rpcUrl: 'http://127.0.0.1:8545',
deployerSeedPhrase:
'test test test test test test test test test test test junk',
// Uncomment this with the url of a bundler to enable using an external
// bundler (sometimes this is the same as rpcUrl). Otherwise, a bundler will
// be simulated inside the library.
// bundlerRpcUrl: '',
requirePermission: false,
// These contracts will be deployed deterministically by default. However,
// if you need to interop with a specific existing deployment, you'll need
// to specify the contracts here:
// externalContracts: {
// entryPoint: '0x...',
// blsSignatureAggregator: '0x...',
// addressRegistry: '0x...',
// }
// (Other contracts will still be deterministically deployed, but they
// shouldn't be required for interop purposes.)
};
export default config;

View File

@@ -13,6 +13,7 @@ WaxInPage.addStylesheet();
const waxInPage = new WaxInPage({
rpcUrl: config.rpcUrl,
bundlerRpcUrl: config.bundlerRpcUrl,
externalContracts: config.externalContracts,
});
waxInPage.attachGlobals();

View File

@@ -1,12 +1,34 @@
#!/usr/bin/env tsx
/* eslint-disable no-console */
import concurrently from 'concurrently';
import config from '../demo/config/config.ts';
const tasks = ['vite'];
let externalNode: boolean;
if (config.rpcUrl === 'http://127.0.0.1:8545') {
tasks.push('yarn --cwd hardhat hardhat node');
try {
await fetch(config.rpcUrl);
externalNode = true;
} catch (e) {
if ((e as { code: string }).code !== 'ECONNREFUSED') {
throw e;
}
externalNode = false;
tasks.push('yarn --cwd hardhat hardhat node');
}
} else {
externalNode = true;
}
if (externalNode) {
console.log(`Relying on external node: ${config.rpcUrl}`);
} else {
console.log('Starting dev node');
}
await concurrently(tasks, { killOthers: 'failure' }).result;

View File

@@ -130,6 +130,10 @@ const Button = ({
return;
}
if ('key' in e && e.key !== 'Enter' && e.key !== ' ') {
return;
}
runAsync(async () => {
try {
setLoading(true);

View File

@@ -13,12 +13,14 @@ import { roundUpPseudoFloat } from './helpers/encodeUtils';
// We need a UserOperation in order to estimate the gas fields of a
// UserOperation, so we use these values as placeholders.
const temporaryEstimationGas = '0x012345';
const temporaryEstimationGas = '0x01234567';
const temporarySignature = [
'0x',
'123456fe2807660c417ca1a38760342fa70135fcab89a8c7c879a77da8ce7a0b5a3805735e',
'95170906b11c6f30dcc74e463e1e6990c68a3998a7271b728b123456',
].join('');
const verificationGasLimitBuffer = 2000n;
const preVerificationGasBuffer = 1000n;
type StrictUserOperation = {
sender: string;
@@ -183,9 +185,15 @@ export default class EthereumApi {
return {
...userOp,
callGasLimit: roundUpPseudoFloat(userOp.callGasLimit),
verificationGasLimit: `0x${roundUpPseudoFloat(
BigInt(userOp.verificationGasLimit),
).toString(16)}`,
preVerificationGas: `0x${roundUpPseudoFloat(
BigInt(userOp.preVerificationGas),
).toString(16)}`,
maxFeePerGas: roundUpPseudoFloat(userOp.maxFeePerGas),
maxPriorityFeePerGas: roundUpPseudoFloat(userOp.maxPriorityFeePerGas),
callGasLimit: roundUpPseudoFloat(userOp.callGasLimit),
};
}
@@ -342,7 +350,7 @@ export default class EthereumApi {
callData,
callGasLimit: actions.map((a) => BigInt(a.gas)).reduce((a, b) => a + b),
verificationGasLimit: temporaryEstimationGas,
preVerificationGas: temporaryEstimationGas,
preVerificationGas: '0x0',
maxFeePerGas,
maxPriorityFeePerGas,
paymasterAndData: '0x',
@@ -360,8 +368,14 @@ export default class EthereumApi {
params: [userOp, await contracts.entryPoint.getAddress()],
});
userOp.verificationGasLimit = verificationGasLimit;
userOp.preVerificationGas = preVerificationGas;
userOp.verificationGasLimit = `0x${(
BigInt(verificationGasLimit) + verificationGasLimitBuffer
).toString(16)}`;
userOp.preVerificationGas = `0x${(
BigInt(preVerificationGas) + preVerificationGasBuffer
).toString(16)}`;
userOp = this.#maybeRoundUpPseudoFloats(userOp);
userOpHash = await this.#calculateUserOpHash(userOp);

View File

@@ -1,7 +1,7 @@
import z from 'zod';
class JsonRpcError extends Error {
code: number;
code: number | string;
data: unknown;
constructor({ code, data, message }: RawJsonRpcError) {
@@ -17,7 +17,7 @@ class JsonRpcError extends Error {
}
const RawJsonRpcError = z.object({
code: z.number().int(),
code: z.union([z.number().int(), z.string()]),
data: z.unknown(),
message: z.string(),
});

View File

@@ -50,6 +50,7 @@ import measureCalldataGas from './measureCalldataGas';
import DeterministicDeployer, {
DeterministicDeploymentViewer,
} from '../lib-ts/deterministic-deployer/DeterministicDeployer';
import ConfigType from '../demo/config/ConfigType';
type Config = {
logRequests?: boolean;
@@ -58,6 +59,7 @@ type Config = {
deployContractsIfNeeded: boolean;
ethersPollingInterval?: number;
useTopLevelCompression?: boolean;
externalContracts?: ConfigType['externalContracts'];
};
const defaultConfig: Config = {
@@ -71,6 +73,7 @@ let ethersDefaultPollingInterval = 4000;
type ConstructorOptions = {
rpcUrl: string;
bundlerRpcUrl?: string;
externalContracts?: ConfigType['externalContracts'];
storage?: WaxStorage;
};
@@ -103,6 +106,7 @@ export default class WaxInPage {
constructor({
rpcUrl,
bundlerRpcUrl,
externalContracts,
storage = makeLocalWaxStorage(),
}: ConstructorOptions) {
let bundler: IBundler;
@@ -113,6 +117,8 @@ export default class WaxInPage {
bundler = new NetworkBundler(bundlerRpcUrl);
}
this.#config.externalContracts =
externalContracts ?? this.#config.externalContracts;
this.ethereum = new EthereumApi(rpcUrl, this, bundler);
this.storage = storage;
this.ethersProvider = new ethers.BrowserProvider(this.ethereum);
@@ -194,15 +200,48 @@ export default class WaxInPage {
chainId,
);
const assumedEntryPoint = viewer.connectAssume(EntryPoint__factory, []);
const assumedAddressRegistry = viewer.connectAssume(
AddressRegistry__factory,
[],
);
const wallet = await this.requestAdminAccount('deploy-contracts');
const assumedBlsOpen = viewer.connectAssume(BLSOpen__factory, []);
let assumedEntryPoint: EntryPoint;
let assumedAddressRegistry: AddressRegistry;
let assumedBlsSignatureAggregator: BLSSignatureAggregator;
if (this.#config.externalContracts) {
assumedEntryPoint = EntryPoint__factory.connect(
this.#config.externalContracts.entryPoint,
wallet,
);
assumedBlsSignatureAggregator = BLSSignatureAggregator__factory.connect(
this.#config.externalContracts.blsSignatureAggregator,
wallet,
);
assumedAddressRegistry = AddressRegistry__factory.connect(
this.#config.externalContracts.addressRegistry,
wallet,
);
} else {
assumedEntryPoint = viewer.connectAssume(EntryPoint__factory, []);
assumedAddressRegistry = viewer.connectAssume(
AddressRegistry__factory,
[],
);
assumedBlsSignatureAggregator = viewer.connectAssume(
DeterministicDeployer.link(BLSSignatureAggregator__factory, [
{
'account-abstraction/contracts/samples/bls/lib/BLSOpen.sol:BLSOpen':
await assumedBlsOpen.getAddress(),
},
]),
[],
);
}
const contracts: Contracts = {
greeter: viewer.connectAssume(Greeter__factory, ['']).connect(runner),
entryPoint: assumedEntryPoint,
@@ -226,15 +265,7 @@ export default class WaxInPage {
[],
),
testToken: viewer.connectAssume(ERC20Mock__factory, []),
blsSignatureAggregator: viewer.connectAssume(
DeterministicDeployer.link(BLSSignatureAggregator__factory, [
{
'account-abstraction/contracts/samples/bls/lib/BLSOpen.sol:BLSOpen':
await assumedBlsOpen.getAddress(),
},
]),
[],
),
blsSignatureAggregator: assumedBlsSignatureAggregator,
};
if (this.#contractsDeployed) {
@@ -250,19 +281,49 @@ export default class WaxInPage {
throw new Error('Contracts not deployed');
}
const wallet = await this.requestAdminAccount('deploy-contracts');
const factory = await DeterministicDeployer.init(wallet);
const entryPoint = await factory.connectOrDeploy(EntryPoint__factory, []);
const addressRegistry = await factory.connectOrDeploy(
AddressRegistry__factory,
[],
);
const blsOpen = await factory.connectOrDeploy(BLSOpen__factory, []);
let entryPoint: EntryPoint;
let blsSignatureAggregator: BLSSignatureAggregator;
let addressRegistry: AddressRegistry;
if (this.#config.externalContracts) {
entryPoint = assumedEntryPoint;
blsSignatureAggregator = assumedBlsSignatureAggregator;
addressRegistry = assumedAddressRegistry;
await Promise.all(
[entryPoint, blsSignatureAggregator, addressRegistry].map(
async (contract) => {
if (!(await this.#checkDeployed(await contract.getAddress()))) {
throw new Error(
`External contract not deployed: ${await contract.getAddress()}`,
);
}
},
),
);
} else {
entryPoint = await factory.connectOrDeploy(EntryPoint__factory, []);
blsSignatureAggregator = await factory.connectOrDeploy(
DeterministicDeployer.link(BLSSignatureAggregator__factory, [
{
'account-abstraction/contracts/samples/bls/lib/BLSOpen.sol:BLSOpen':
await blsOpen.getAddress(),
},
]),
[],
);
addressRegistry = await factory.connectOrDeploy(
AddressRegistry__factory,
[],
);
}
const deployments: {
[C in keyof Contracts]: () => Promise<Contracts[C]>;
} = {
@@ -285,16 +346,7 @@ export default class WaxInPage {
safeECDSARecoveryPlugin: () =>
factory.connectOrDeploy(SafeECDSARecoveryPlugin__factory, []),
testToken: () => factory.connectOrDeploy(ERC20Mock__factory, []),
blsSignatureAggregator: async () =>
factory.connectOrDeploy(
DeterministicDeployer.link(BLSSignatureAggregator__factory, [
{
'account-abstraction/contracts/samples/bls/lib/BLSOpen.sol:BLSOpen':
await blsOpen.getAddress(),
},
]),
[],
),
blsSignatureAggregator: () => Promise.resolve(blsSignatureAggregator),
};
for (const deployment of Object.values(deployments)) {
@@ -306,18 +358,19 @@ export default class WaxInPage {
async #checkDeployments(contracts: Contracts): Promise<boolean> {
const deployFlags = await Promise.all(
Object.values(contracts).map(async (contract) => {
const existingCode = await this.ethersProvider.getCode(
contract.getAddress(),
);
return existingCode !== '0x';
}),
Object.values(contracts).map(
async (contract) =>
await this.#checkDeployed(await contract.getAddress()),
),
);
return deployFlags.every((flag) => flag);
}
async #checkDeployed(address: string): Promise<boolean> {
return (await this.ethersProvider.getCode(address)) !== '0x';
}
async requestAdminAccount(purpose: AdminPurpose): Promise<ethers.Wallet> {
if (this.#adminAccount) {
return this.#adminAccount;

View File

@@ -23,7 +23,6 @@ import {
hexJoin,
hexLen,
lookupAddress,
roundUpPseudoFloat,
} from '../helpers/encodeUtils';
import windowDebug from '../../demo/windowDebug';
import simulateValidation from '../helpers/simulateValidation';
@@ -148,19 +147,13 @@ export default class SimulatedBundler implements IBundler {
],
});
let res = {
return {
preVerificationGas: `0x${(basePreVerificationGas + calldataGas).toString(
16,
)}`,
verificationGasLimit: `0x${verificationGasLimit.toString(16)}`,
callGasLimit,
};
if (this.#waxInPage.getConfig('useTopLevelCompression')) {
res = SimulatedBundler.roundUpGasEstimate(res);
}
return res;
}
async eth_getUserOperationReceipt(
@@ -513,21 +506,6 @@ export default class SimulatedBundler implements IBundler {
parts.push(encodeBytes(calldata));
}
}
static roundUpGasEstimate({
preVerificationGas,
verificationGasLimit,
callGasLimit,
}: EthereumRpc.UserOperationGasEstimate): EthereumRpc.UserOperationGasEstimate {
const roundUp = (x: string) =>
`0x${roundUpPseudoFloat(BigInt(x)).toString(16)}`;
return {
preVerificationGas: roundUp(preVerificationGas),
verificationGasLimit: roundUp(verificationGasLimit),
callGasLimit: roundUp(callGasLimit),
};
}
}
const decompressAndPerformSelector = ethers.FunctionFragment.getSelector(

View File

@@ -10,3 +10,5 @@ cache
cache_hardhat
artifacts
deployedAddresses.backup.json
deployedAddresses.json

View File

@@ -20,7 +20,7 @@ const config: HardhatUserConfig = {
settings: {
optimizer: {
enabled: true,
runs: 200,
runs: 1_000_000,
},
},
},

View File

@@ -1,5 +1,9 @@
import fs from "fs/promises";
import { ethers } from "ethers";
import DeterministicDeployer from "../lib-ts/deterministic-deployer/DeterministicDeployer";
import DeterministicDeployer, {
ContractFactoryConstructor,
DeployParams,
} from "../lib-ts/deterministic-deployer/DeterministicDeployer";
import {
SimulateTxAccessor__factory,
SafeProxyFactory__factory,
@@ -13,11 +17,17 @@ import {
EntryPoint__factory,
BLSSignatureAggregator__factory,
BLSOpen__factory,
HandleOpsCaller__factory,
HandleAggregatedOpsCaller__factory,
AddressRegistry__factory,
} from "../typechain-types";
import makeDevFaster from "../test/e2e/utils/makeDevFaster";
import { TokenCallbackHandler__factory } from "../typechain-types/factories/lib/safe-contracts/contracts/handler/TokenCallbackHandler__factory";
import bundlerConfig from "./../config/bundler.config.json";
// 'test '.repeat(11) + 'absent'
const testAbsentAddress = "0xe8250207B79D7396631bb3aE38a7b457261ae0B6";
async function deploy() {
const { NODE_URL, MNEMONIC } = process.env;
const provider = new ethers.JsonRpcProvider(NODE_URL);
@@ -25,63 +35,140 @@ async function deploy() {
const hdNode = ethers.HDNodeWallet.fromPhrase(MNEMONIC!);
const wallet = new ethers.Wallet(hdNode.privateKey, provider);
const deployer = await DeterministicDeployer.init(wallet);
const recordingDeployer = await RecordingDeployer.init(wallet);
const contractFactories = [
SimulateTxAccessor__factory,
TokenCallbackHandler__factory,
CompatibilityFallbackHandler__factory,
CreateCall__factory,
EntryPoint__factory,
MultiSend__factory,
MultiSendCallOnly__factory,
SignMessageLib__factory,
BLSOpen__factory,
];
for (const contractFactory of contractFactories) {
const contract = await deployer.connectOrDeploy(contractFactory, []);
const contractName = contractFactory.name.split("_")[0];
console.log(`deployed ${contractName} to ${await contract.getAddress()}`);
}
const blsSignatureAggregatorFactory = DeterministicDeployer.link(
const linkedAggregator = DeterministicDeployer.link(
BLSSignatureAggregator__factory,
[
{
"lib/account-abstraction/contracts/samples/bls/lib/BLSOpen.sol:BLSOpen":
deployer.calculateAddress(BLSOpen__factory, []),
recordingDeployer.deployer.calculateAddress(BLSOpen__factory, []),
},
],
);
const blsSignatureAggregator = await deployer.connectOrDeploy(
blsSignatureAggregatorFactory,
[bundlerConfig.entryPoint],
);
console.log(
`deployed ${
BLSSignatureAggregator__factory.name.split("_")[0]
} to ${await blsSignatureAggregator.getAddress()}`,
const deployments = [
["SimulateTxAccessor", SimulateTxAccessor__factory],
["TokenCallbackHandler", TokenCallbackHandler__factory],
["CompatibilityFallbackHandler", CompatibilityFallbackHandler__factory],
["CreateCall", CreateCall__factory],
["EntryPoint", EntryPoint__factory],
["MultiSend", MultiSend__factory],
["MultiSendCallOnly", MultiSendCallOnly__factory],
["SignMessageLib", SignMessageLib__factory],
["BLSOpen", BLSOpen__factory],
["BLSSignatureAggregator", linkedAggregator],
["AddressRegistry", AddressRegistry__factory],
] as const;
for (const [name, contractFactory] of deployments) {
await recordingDeployer.deploy(name, contractFactory, []);
}
const handleOpsCaller = await recordingDeployer.deploy(
"HandleOpsCaller",
HandleOpsCaller__factory,
[
recordingDeployer.deployer.calculateAddress(EntryPoint__factory, []),
testAbsentAddress,
recordingDeployer.deployer.calculateAddress(AddressRegistry__factory, []),
],
);
const safeDeployer = await DeterministicDeployer.initSafeVersion(wallet);
const handleAggregatedOpsCaller = await recordingDeployer.deploy(
"HandleAggregatedOpsCaller",
HandleAggregatedOpsCaller__factory,
[
recordingDeployer.deployer.calculateAddress(EntryPoint__factory, []),
testAbsentAddress,
recordingDeployer.deployer.calculateAddress(linkedAggregator, []),
recordingDeployer.deployer.calculateAddress(AddressRegistry__factory, []),
],
);
void handleOpsCaller;
void handleAggregatedOpsCaller;
const safeContractFactories = [
SafeProxyFactory__factory,
SafeL2__factory,
Safe__factory,
];
["SafeProxyFactory", SafeProxyFactory__factory],
["SafeL2", SafeL2__factory],
["Safe", Safe__factory],
] as const;
for (const contractFactory of safeContractFactories) {
const contract = await safeDeployer.connectOrDeploy(contractFactory, []);
const contractName = contractFactory.name.split("_")[0];
console.log(`deployed ${contractName} to ${await contract.getAddress()}`);
for (const [name, contractFactory] of safeContractFactories) {
await recordingDeployer.deployWithSafeDeployer(name, contractFactory, []);
}
await recordingDeployer.finish();
}
deploy().catch((error: Error) => {
console.error(error);
process.exitCode = 1;
});
class RecordingDeployer {
deployments: Record<string, string> = {};
private constructor(
public deployer: DeterministicDeployer,
public safeDeployer: DeterministicDeployer,
) {}
static async init(wallet: ethers.Wallet): Promise<RecordingDeployer> {
const deployer = await DeterministicDeployer.init(wallet);
const safeDeployer = await DeterministicDeployer.initSafeVersion(wallet);
try {
await fs.rename(
"deployedAddresses.json",
"deployedAddresses.backup.json",
);
} catch (e) {
if ((e as { code: string }).code !== "ENOENT") {
throw e;
}
}
return new RecordingDeployer(deployer, safeDeployer);
}
async deploy<CFC extends ContractFactoryConstructor>(
name: string,
factory: CFC,
args: DeployParams<CFC>,
) {
return this.deployImpl(name, factory, args, this.deployer);
}
async deployWithSafeDeployer<CFC extends ContractFactoryConstructor>(
name: string,
factory: CFC,
args: DeployParams<CFC>,
) {
return this.deployImpl(name, factory, args, this.safeDeployer);
}
private async deployImpl<CFC extends ContractFactoryConstructor>(
name: string,
factory: CFC,
args: DeployParams<CFC>,
deployer: DeterministicDeployer,
) {
const contract = await deployer.connectOrDeploy(factory, args);
const address = await contract.getAddress();
console.log(`Deployed ${name} to ${address}`);
this.deployments[name] = address;
return contract;
}
async finish() {
await fs.writeFile(
"deployedAddresses.json",
JSON.stringify(this.deployments, null, 2),
);
await fs.rm("deployedAddresses.backup.json", { force: true });
}
}

View File

@@ -17,7 +17,6 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
set -meuo pipefail
function cleanup {
docker stop $CONTAINER || true
jobs -p | xargs kill
}

View File

@@ -151,7 +151,7 @@ contract SafeZkEmailRecoveryPlugin_Integration_Test is TestHelper {
);
emailProof.timestamp = block.timestamp;
emailProof
.maskedSubject = "Accept guardian request for 0x78cA0A67bF6Cbe8Bf2429f0c7934eE5Dd687a32c";
.maskedSubject = "Accept guardian request for 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9";
emailProof.emailNullifier = keccak256(abi.encode("nullifier 1"));
emailProof.accountSalt = accountSalt;
emailProof.isCodeExist = true;
@@ -185,7 +185,7 @@ contract SafeZkEmailRecoveryPlugin_Integration_Test is TestHelper {
);
emailProof.timestamp = block.timestamp + 1;
emailProof
.maskedSubject = "Update owner to 0xDdF4497d39b10cf50Af640942cc15233970dA0c2 on account 0x78cA0A67bF6Cbe8Bf2429f0c7934eE5Dd687a32c";
.maskedSubject = "Update owner to 0xDdF4497d39b10cf50Af640942cc15233970dA0c2 on account 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9";
emailProof.emailNullifier = keccak256(abi.encode("nullifier 2"));
emailProof.accountSalt = accountSalt;
require(