From 18096b4c7be04915f42ad6d366087c2ef50e74c7 Mon Sep 17 00:00:00 2001 From: jj1980a Date: Thu, 9 May 2024 17:14:20 +0100 Subject: [PATCH] simulation hooks --- jest.config.ts | 1 + src/abi/Atlas.json | 17 +++ src/abi/Multicall3.json | 260 ++++++++++++++++++++++++++++++++++ src/abi/Simulator.json | 227 +++++++++++++++++++++++++++++ src/config/chain.ts | 18 +++ src/index.ts | 2 +- src/relay/base.ts | 46 ++++-- src/relay/fastlane.ts | 2 + src/relay/hooks/base.ts | 20 ++- src/relay/hooks/simulation.ts | 152 +++++++++++++++++++- src/relay/mock.ts | 12 +- test/sdk.test.ts | 7 +- 12 files changed, 725 insertions(+), 39 deletions(-) create mode 100644 src/abi/Multicall3.json create mode 100644 src/abi/Simulator.json diff --git a/jest.config.ts b/jest.config.ts index a5f61e1..19f458b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,4 +1,5 @@ export default { preset: "ts-jest/presets/js-with-ts-esm", moduleDirectories: ["node_modules", ""], + workerThreads: true, }; diff --git a/src/abi/Atlas.json b/src/abi/Atlas.json index e42b3e2..163b1ae 100644 --- a/src/abi/Atlas.json +++ b/src/abi/Atlas.json @@ -89,5 +89,22 @@ { "name": "auctionWon", "type": "bool", "internalType": "bool" } ], "stateMutability": "payable" + }, + { + "type": "function", + "name": "accessData", + "inputs": [{ "name": "", "type": "address", "internalType": "address" }], + "outputs": [ + { "name": "bonded", "type": "uint112", "internalType": "uint112" }, + { + "name": "lastAccessedBlock", + "type": "uint32", + "internalType": "uint32" + }, + { "name": "auctionWins", "type": "uint24", "internalType": "uint24" }, + { "name": "auctionFails", "type": "uint24", "internalType": "uint24" }, + { "name": "totalGasUsed", "type": "uint64", "internalType": "uint64" } + ], + "stateMutability": "view" } ] diff --git a/src/abi/Multicall3.json b/src/abi/Multicall3.json new file mode 100644 index 0000000..332ac09 --- /dev/null +++ b/src/abi/Multicall3.json @@ -0,0 +1,260 @@ +[ + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, + { "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bool", "name": "allowFailure", "type": "bool" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bool", "name": "allowFailure", "type": "bool" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call3Value[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3Value", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getBasefee", + "outputs": [ + { "internalType": "uint256", "name": "basefee", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" } + ], + "name": "getBlockHash", + "outputs": [ + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { "internalType": "uint256", "name": "chainid", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { "internalType": "address", "name": "coinbase", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [ + { "internalType": "uint256", "name": "difficulty", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { "internalType": "uint256", "name": "gaslimit", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { "internalType": "uint256", "name": "timestamp", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "addr", "type": "address" } + ], + "name": "getEthBalance", + "outputs": [ + { "internalType": "uint256", "name": "balance", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/src/abi/Simulator.json b/src/abi/Simulator.json new file mode 100644 index 0000000..569f8d2 --- /dev/null +++ b/src/abi/Simulator.json @@ -0,0 +1,227 @@ +[ + { + "type": "function", + "name": "simSolverCall", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct UserOperation", + "components": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "gas", "type": "uint256", "internalType": "uint256" }, + { + "name": "maxFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "dapp", "type": "address", "internalType": "address" }, + { "name": "control", "type": "address", "internalType": "address" }, + { + "name": "sessionKey", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ] + }, + { + "name": "solverOp", + "type": "tuple", + "internalType": "struct SolverOperation", + "components": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "gas", "type": "uint256", "internalType": "uint256" }, + { + "name": "maxFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "solver", "type": "address", "internalType": "address" }, + { "name": "control", "type": "address", "internalType": "address" }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { "name": "bidToken", "type": "address", "internalType": "address" }, + { "name": "bidAmount", "type": "uint256", "internalType": "uint256" }, + { "name": "data", "type": "bytes", "internalType": "bytes" }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ] + }, + { + "name": "dAppOp", + "type": "tuple", + "internalType": "struct DAppOperation", + "components": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "gas", "type": "uint256", "internalType": "uint256" }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "control", "type": "address", "internalType": "address" }, + { "name": "bundler", "type": "address", "internalType": "address" }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "callChainHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "success", "type": "bool", "internalType": "bool" }, + { "name": "simResult", "type": "uint8", "internalType": "enum Result" }, + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "simSolverCalls", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct UserOperation", + "components": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "gas", "type": "uint256", "internalType": "uint256" }, + { + "name": "maxFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "dapp", "type": "address", "internalType": "address" }, + { "name": "control", "type": "address", "internalType": "address" }, + { + "name": "sessionKey", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ] + }, + { + "name": "solverOps", + "type": "tuple[]", + "internalType": "struct SolverOperation[]", + "components": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "gas", "type": "uint256", "internalType": "uint256" }, + { + "name": "maxFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "solver", "type": "address", "internalType": "address" }, + { "name": "control", "type": "address", "internalType": "address" }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { "name": "bidToken", "type": "address", "internalType": "address" }, + { "name": "bidAmount", "type": "uint256", "internalType": "uint256" }, + { "name": "data", "type": "bytes", "internalType": "bytes" }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ] + }, + { + "name": "dAppOp", + "type": "tuple", + "internalType": "struct DAppOperation", + "components": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "gas", "type": "uint256", "internalType": "uint256" }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "control", "type": "address", "internalType": "address" }, + { "name": "bundler", "type": "address", "internalType": "address" }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "callChainHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "success", "type": "bool", "internalType": "bool" }, + { "name": "simResult", "type": "uint8", "internalType": "enum Result" }, + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "simUserOperation", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct UserOperation", + "components": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "gas", "type": "uint256", "internalType": "uint256" }, + { + "name": "maxFeePerGas", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "dapp", "type": "address", "internalType": "address" }, + { "name": "control", "type": "address", "internalType": "address" }, + { + "name": "sessionKey", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "success", "type": "bool", "internalType": "bool" }, + { "name": "simResult", "type": "uint8", "internalType": "enum Result" }, + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "payable" + } +] diff --git a/src/config/chain.ts b/src/config/chain.ts index c4743a4..cae7da1 100644 --- a/src/config/chain.ts +++ b/src/config/chain.ts @@ -11,6 +11,12 @@ export interface ChainConfig { sorter: { address: string; }; + simulator: { + address: string; + }; + multicall3: { + address: string; + }; }; eip712Domain: TypedDataDomain; } @@ -28,6 +34,12 @@ export const chainConfig: { [chainId: number]: ChainConfig } = { sorter: { address: "", }, + simulator: { + address: "", + }, + multicall3: { + address: "", + }, }, eip712Domain: { name: "AtlasVerification", @@ -49,6 +61,12 @@ export const chainConfig: { [chainId: number]: ChainConfig } = { sorter: { address: "0x09c69Fefd937d2B05fB8a313120fCA5176b3Aa1d", }, + simulator: { + address: "0xa76a0CD24769241F890B322c39ABDd52aa962094", + }, + multicall3: { + address: "0xcA11bde05977b3631167028862bE2a173976CA11", + }, }, eip712Domain: { name: "AtlasVerification", diff --git a/src/index.ts b/src/index.ts index 10b310e..16fae99 100644 --- a/src/index.ts +++ b/src/index.ts @@ -212,7 +212,7 @@ export class AtlasSdk { // Get the solver operations const solverOps: SolverOperation[] = - await this.operationsRelay.getSolverOperations(userOphash, true); + await this.operationsRelay.getSolverOperations(userOp, userOphash, true); if (solverOps.length === 0 && !flagZeroSolvers(callConfig)) { throw new Error("No solver operations returned"); diff --git a/src/relay/base.ts b/src/relay/base.ts index defcd7d..9ab2efe 100644 --- a/src/relay/base.ts +++ b/src/relay/base.ts @@ -27,19 +27,22 @@ export interface IOperationsRelay { /** * Get solver operations for a user operation previously submitted * @summary Get solver operations for a user operation previously submitted + * @param {UserOperation} userOp The user operation * @param {string} userOpHash The hash of the user operation * @param {boolean} [wait] Hold the request until having a response * @param {*} [extra] Extra parameters * @returns {Promise} The solver operations */ getSolverOperations( - userOpHash: any, + userOp: UserOperation, + userOpHash: string, wait?: boolean, extra?: any ): Promise; _getSolverOperations( - userOpHash: any, + userOp: UserOperation, + userOpHash: string, wait?: boolean, extra?: any ): Promise; @@ -63,9 +66,17 @@ export interface IOperationsRelay { * @param {*} [extra] Extra parameters * @returns {Promise} The Atlas transaction hash */ - getBundleHash(userOpHash: any, wait?: boolean, extra?: any): Promise; + getBundleHash( + userOpHash: string, + wait?: boolean, + extra?: any + ): Promise; - _getBundleHash(userOpHash: any, wait?: boolean, extra?: any): Promise; + _getBundleHash( + userOpHash: string, + wait?: boolean, + extra?: any + ): Promise; } export abstract class BaseOperationRelay implements IOperationsRelay { @@ -105,21 +116,33 @@ export abstract class BaseOperationRelay implements IOperationsRelay { } async getSolverOperations( - userOpHash: any, + userOp: UserOperation, + userOpHash: string, wait?: boolean, extra?: any ): Promise { // Pre hooks for (const hooksController of this.hooksControllers) { - userOpHash = await hooksController.preGetSolverOperations(userOpHash); + [userOp, userOpHash] = await hooksController.preGetSolverOperations( + userOp, + userOpHash + ); } // Implemented by subclass - let solverOps = await this._getSolverOperations(userOpHash, wait, extra); + let solverOps = await this._getSolverOperations( + userOp, + userOpHash, + wait, + extra + ); // Post hooks for (const hooksController of this.hooksControllers) { - solverOps = await hooksController.postGetSolverOperations(solverOps); + [userOp, solverOps] = await hooksController.postGetSolverOperations( + userOp, + solverOps + ); } return solverOps; @@ -143,7 +166,7 @@ export abstract class BaseOperationRelay implements IOperationsRelay { } async getBundleHash( - userOpHash: any, + userOpHash: string, wait?: boolean, extra?: any ): Promise { @@ -172,7 +195,8 @@ export abstract class BaseOperationRelay implements IOperationsRelay { } async _getSolverOperations( - userOpHash: any, + userOp: UserOperation, + userOpHash: string, wait?: boolean, extra?: any ): Promise { @@ -184,7 +208,7 @@ export abstract class BaseOperationRelay implements IOperationsRelay { } async _getBundleHash( - userOpHash: any, + userOpHash: string, wait?: boolean, extra?: any ): Promise { diff --git a/src/relay/fastlane.ts b/src/relay/fastlane.ts index 6ad26fd..e60eab4 100644 --- a/src/relay/fastlane.ts +++ b/src/relay/fastlane.ts @@ -93,12 +93,14 @@ export class FastlaneOperationsRelay extends BaseOperationRelay { /** * Get solver operations for a user operation previously submitted * @summary Get solver operations for a user operation previously submitted + * @param {UserOperation} [userOp] The user operation * @param {string} userOpHash The hash of the user operation * @param {boolean} [wait] Hold the request until having a response * @param {*} [extra] Extra parameters * @returns {Promise} The solver operations */ public async _getSolverOperations( + _: UserOperation, userOpHash: string, wait?: boolean, extra?: any diff --git a/src/relay/hooks/base.ts b/src/relay/hooks/base.ts index f80d9ec..0741051 100644 --- a/src/relay/hooks/base.ts +++ b/src/relay/hooks/base.ts @@ -12,11 +12,15 @@ export interface IHooksController { userOphash: string ): Promise<[UserOperation, string]>; - preGetSolverOperations(userOphash: string): Promise; + preGetSolverOperations( + userOp: UserOperation, + userOphash: string + ): Promise<[UserOperation, string]>; postGetSolverOperations( + userOp: UserOperation, solverOps: SolverOperation[] - ): Promise; + ): Promise<[UserOperation, SolverOperation[]]>; preSubmitBundle(bundleOps: Bundle): Promise; @@ -51,14 +55,18 @@ export abstract class BaseHooksController implements IHooksController { return [userOp, userOphash]; } - async preGetSolverOperations(userOphash: string): Promise { - return userOphash; + async preGetSolverOperations( + userOp: UserOperation, + userOphash: string + ): Promise<[UserOperation, string]> { + return [userOp, userOphash]; } async postGetSolverOperations( + userOp: UserOperation, solverOps: SolverOperation[] - ): Promise { - return solverOps; + ): Promise<[UserOperation, SolverOperation[]]> { + return [userOp, solverOps]; } async preSubmitBundle(bundleOps: Bundle): Promise { diff --git a/src/relay/hooks/simulation.ts b/src/relay/hooks/simulation.ts index 5990d4d..89fa6c3 100644 --- a/src/relay/hooks/simulation.ts +++ b/src/relay/hooks/simulation.ts @@ -1,30 +1,168 @@ -import { AbstractProvider } from "ethers"; -import { UserOperation, SolverOperation, Bundle } from "../../operation"; +import { + AbstractProvider, + Contract, + VoidSigner, + ZeroAddress, + ZeroHash, +} from "ethers"; +import { + UserOperation, + SolverOperation, + Bundle, + OperationBuilder, + ZeroBytes, +} from "../../operation"; import { BaseHooksController } from "./base"; import { chainConfig } from "../../config"; +import atlasAbi from "../../abi/Atlas.json"; +import simulatorAbi from "../../abi/Simulator.json"; +import multicall3Abi from "../../abi/Multicall3.json"; export class SimulationHooksController extends BaseHooksController { + private atlas: Contract; + private simulator: Contract; + private multicall3: Contract; + private maxSolutions: number = 10; + constructor(provider: AbstractProvider, chainId: number) { super(provider, chainId); + this.atlas = new Contract( + chainConfig[chainId].contracts.atlas.address, + atlasAbi, + provider + ); + this.simulator = new Contract( + chainConfig[chainId].contracts.simulator.address, + simulatorAbi, + provider + ); + this.multicall3 = new Contract( + chainConfig[chainId].contracts.multicall3.address, + multicall3Abi, + provider + ); } async preSubmitUserOperation( userOp: UserOperation, hints: string[] ): Promise<[UserOperation, string[]]> { - console.log("preSubmitUserOperation hooks"); + let [success, result, validCallsResult] = await this.simulator + .getFunction("simUserOperation") + .staticCall(userOp.toStruct()); + + if (!success) { + throw new Error( + `user operation failed simulation, result: ${result}, validCallsResult: ${validCallsResult}` + ); + } + return [userOp, hints]; } async postGetSolverOperations( + userOp: UserOperation, solverOps: SolverOperation[] - ): Promise { - console.log("postGetSolverOperations hooks"); - return solverOps; + ): Promise<[UserOperation, SolverOperation[]]> { + let sortedSolverOps: SolverOperation[] = solverOps.slice(); + const atlasAddress = await this.atlas.getAddress(); + const simulatorAddress = await this.simulator.getAddress(); + + // Get scores (multicall) + let calls = sortedSolverOps.map((solverOp) => { + return { + target: atlasAddress, + allowFailure: true, + callData: this.atlas.interface.encodeFunctionData("accessData", [ + solverOp.getField("from").value, + ]), + }; + }); + let results = await this.multicall3 + .getFunction("aggregate3") + .staticCall(calls); + for (let i = 0; i < results.length; i++) { + if (!results[i].success) { + console.log("Failed to get stats for solver operation", i); + continue; + } + const stats = this.atlas.interface.decodeFunctionResult( + "accessData", + results[i].returnData + ); + const auctionWins = Number(stats[2]); + const auctionFails = Number(stats[3]); + const total = auctionWins + auctionFails; + sortedSolverOps[i].score = total === 0 ? 0 : (auctionWins * 100) / total; + } + + // Sort by score + sortedSolverOps.sort((a, b) => { + return a.score - b.score; + }); + + // Keep only the best solutions + sortedSolverOps = sortedSolverOps.slice(0, this.maxSolutions); + + // Simulate (multicall) + calls = sortedSolverOps.map((solverOp) => { + return { + target: simulatorAddress, + allowFailure: true, + callData: this.simulator.interface.encodeFunctionData("simSolverCall", [ + userOp.toStruct(), + solverOp.toStruct(), + OperationBuilder.newDAppOperation({ + from: ZeroAddress, + to: ZeroAddress, + value: 0n, + gas: 0n, + nonce: 0n, + deadline: userOp.getField("deadline").value as bigint, + control: userOp.getField("control").value as string, + bundler: ZeroAddress, + userOpHash: ZeroHash, + callChainHash: ZeroHash, + signature: ZeroBytes, + }).toStruct(), + ]), + }; + }); + results = await this.multicall3.getFunction("aggregate3").staticCall(calls); + let simulatedSolverOps: SolverOperation[] = []; + for (let i = 0; i < results.length; i++) { + if (!results[i].success) { + continue; + } + const [success, ,] = this.simulator.interface.decodeFunctionResult( + "simSolverCall", + results[i].returnData + ); + if (!success) { + continue; + } + simulatedSolverOps.push(sortedSolverOps[i]); + } + + return [userOp, simulatedSolverOps]; } async preSubmitBundle(bundleOps: Bundle): Promise { - console.log("preSubmitBundle hooks"); + // Simulation will throw if the bundle is invalid + await this.atlas + .connect( + new VoidSigner( + bundleOps.dAppOperation.getField("bundler").value as string, + this.provider + ) + ) + .getFunction("metacall") + .staticCall( + bundleOps.userOperation.toStruct(), + bundleOps.solverOperations.map((solverOp) => solverOp.toStruct()), + bundleOps.dAppOperation.toStruct() + ); + return bundleOps; } } diff --git a/src/relay/mock.ts b/src/relay/mock.ts index dd72511..f161de4 100644 --- a/src/relay/mock.ts +++ b/src/relay/mock.ts @@ -4,7 +4,6 @@ import { UserOperation, SolverOperation, Bundle } from "../operation"; import { keccak256, ZeroAddress } from "ethers"; export class MockOperationsRelay extends BaseOperationRelay { - private submittedUserOps: { [key: string]: UserOperation } = {}; private submittedBundles: { [key: string]: Bundle } = {}; constructor() { @@ -24,29 +23,24 @@ export class MockOperationsRelay extends BaseOperationRelay { hints: string[], extra?: any ): Promise { - const userOpHash = keccak256(userOp.abiEncode()); - this.submittedUserOps[userOpHash] = userOp; - return userOpHash; + return keccak256(userOp.abiEncode()); } /** * Get solver operations for a user operation previously submitted * @summary Get solver operations for a user operation previously submitted + * @param {UserOperation} userOp The user operation * @param {string} userOpHash The hash of the user operation * @param {boolean} [wait] Hold the request until having a response * @param {*} [extra] Extra parameters * @returns {Promise} The solver operations */ public async _getSolverOperations( + userOp: UserOperation, userOpHash: string, wait?: boolean, extra?: any ): Promise { - const userOp = this.submittedUserOps[userOpHash]; - if (userOp === undefined) { - throw "User operation not found"; - } - const solverOps: SolverOperation[] = []; for (let i = 0; i < Math.floor(Math.random() * 5 + 1); i++) { solverOps.push( diff --git a/test/sdk.test.ts b/test/sdk.test.ts index a8a3ef5..426b0a8 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -10,15 +10,12 @@ import { MockOperationsRelay } from "../src/relay/mock"; import { OperationBuilder, ZeroBytes } from "../src/operation"; import { validateBytes32 } from "../src/utils"; import { chainConfig } from "../src/config"; -import { SimulationHooksController } from "../src/relay/hooks/simulation"; describe("Atlas SDK main tests", () => { const chainId = 11155111; const provider = new JsonRpcProvider("https://rpc.sepolia.org/", chainId); const opsRelay = new MockOperationsRelay(); - const sdk = new AtlasSdk(provider, chainId, opsRelay, [ - SimulationHooksController, - ]); + const sdk = new AtlasSdk(provider, chainId, opsRelay); const testDAppControl = "0xe9c7bEAF3da67d3FB00708ADAE8ab62e578246d7"; const testCallConfig = 97520; @@ -34,7 +31,7 @@ describe("Atlas SDK main tests", () => { gas: 100000n, maxFeePerGas: 30000000000n, deadline: 0n, - dapp: chainConfig[chainId].contracts.atlas.address, + dapp: testDAppControl, control: testDAppControl, data: "0x83a6992a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000007439e9bb6d8a84dd3a23fe621a30f95403f87fb90000000000000000000000000000000000000000000000000000b5e620f480000000000000000000000000007b79995e5f793a07bc00c21412e50ecae098e7f9000000000000000000000000000000000000000000000000000000e8d4a510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000", };