import { arrayify , defaultAbiCoder , keccak256 } from "ethers/lib/utils" ; import { BigNumber , Contract , ethers , Signer , Wallet } from "ethers" ; import { AddressZero , callDataCost , rethrow } from "./testutils" ; import { ecsign , toRpcSig , keccak256 as keccak256_buffer } from "ethereumjs-util" ; import { EntryPoint , } from '../typechain' ; import { UserOperation } from "./UserOperation" ; function encode ( typevalues , forSignature ) { const types = typevalues . map ( typevalue => typevalue . type == 'bytes' && forSignature ? 'bytes32' : typevalue . type ) ; const values = typevalues . map ( ( typevalue ) => typevalue . type == 'bytes' && forSignature ? keccak256 ( typevalue . val ) : typevalue . val ) ; return defaultAbiCoder . encode ( types , values ) ; } export function packUserOp ( op , forSignature = true ) { if ( forSignature ) { const userOpType = { "components" : [ { "type" : "address" , "name" : "sender" } , { "type" : "uint256" , "name" : "nonce" } , { "type" : "bytes" , "name" : "initCode" } , { "type" : "bytes" , "name" : "callData" } , { "type" : "uint256" , "name" : "callGas" } , { "type" : "uint256" , "name" : "verificationGas" } , { "type" : "uint256" , "name" : "preVerificationGas" } , { "type" : "uint256" , "name" : "maxFeePerGas" } , { "type" : "uint256" , "name" : "maxPriorityFeePerGas" } , { "type" : "address" , "name" : "paymaster" } , { "type" : "bytes" , "name" : "paymasterData" } , { "type" : "bytes" , "name" : "signature" } ] , "name" : "userOp" , "type" : "tuple" } ; let encoded = defaultAbiCoder . encode ( [ userOpType as any ] , [ { ... op , signature : '0x' } ] ) ; encoded = '0x' + encoded . slice ( 66 , encoded . length - 64 ) ; return encoded ; } let typevalues = [ { type : 'address' , val : op . sender } , { type : 'uint256' , val : op . nonce } , { type : 'bytes' , val : op . initCode } , { type : 'bytes' , val : op . callData } , { type : 'uint256' , val : op . callGas } , { type : 'uint256' , val : op . verificationGas } , { type : 'uint256' , val : op . preVerificationGas } , { type : 'uint256' , val : op . maxFeePerGas } , { type : 'uint256' , val : op . maxPriorityFeePerGas } , { type : 'address' , val : op . paymaster } , { type : 'bytes' , val : op . paymasterData } ] ; if ( forSignature ) { } else { typevalues . push ( { type : 'bytes' , val : op . signature } ) ; } return encode ( typevalues , forSignature ) ; } export function packUserOp1 ( op ) { return defaultAbiCoder . encode ( [ 'address' , 'uint256' , 'bytes32' , 'bytes32' , 'uint256' , 'uint' , 'uint' , 'uint256' , 'uint256' , 'address' , 'bytes32' , ] , [ op . sender , op . nonce , keccak256 ( op . initCode ) , keccak256 ( op . callData ) , op . callGas , op . verificationGas , op . preVerificationGas , op . maxFeePerGas , op . maxPriorityFeePerGas , op . paymaster , keccak256 ( op . paymasterData ) ] ) ; } export function getRequestId ( op , entryPoint , chainId ) { const userOpHash = keccak256 ( packUserOp ( op , true ) ) ; const enc = defaultAbiCoder . encode ( [ 'bytes32' , 'address' , 'uint256' ] , [ userOpHash , entryPoint , chainId ] ) ; return keccak256 ( enc ) ; } export const DefaultsForUserOp = { sender : AddressZero , nonce : 0 , initCode : '0x' , callData : '0x' , callGas : 0 , verificationGas : 100000 , preVerificationGas : 21000 , maxFeePerGas : 0 , maxPriorityFeePerGas : 1e9 , paymaster : AddressZero , paymasterData : '0x' , signature : '0x' } ; export function signUserOp ( op , signer , entryPoint , chainId ) { const message = getRequestId ( op , entryPoint , chainId ) ; let msg1 = Buffer . concat ( [ Buffer . from ( "\x19Ethereum Signed Message:\n32" , 'ascii' ) , Buffer . from ( arrayify ( message ) ) ] ) ; const sig = ecsign ( keccak256_buffer ( msg1 ) , Buffer . from ( arrayify ( signer . privateKey ) ) ) ; let signedMessage1 = toRpcSig ( sig . v , sig . r , sig . s ) ; return { ... op , signature : signedMessage1 } ; } export function fillUserOp ( op , defaults = DefaultsForUserOp ) { const partial = { ... op } ; for ( let key in partial ) { if ( partial [ key ] == undefined ) { delete partial [ key ] ; } } const filled = { ... defaults , ... partial } ; return filled ; } export async function fillAndSign ( op , signer , entryPoint ? ) { let op1 = { ... op } ; let provider = entryPoint ?. provider ; if ( op . initCode != null ) { if ( ! op1 . nonce ) op1 . nonce = 0 ; if ( op1 . sender == null ) { if ( entryPoint == null ) throw new Error ( 'must have entryPoint to calc sender address from initCode' ) ; op1 . sender = await entryPoint ! . getSenderAddress ( op . initCode , op1 . nonce ) ; } if ( op1 . verificationGas == null ) { op1 . verificationGas = BigNumber . from ( DefaultsForUserOp . verificationGas ) . add ( 32000 + 200 * op . initCode . length / 2 ) ; } } if ( op1 . nonce == null ) { if ( provider == null ) throw new Error ( 'must have entryPoint to autofill nonce' ) ; const c = new Contract ( op . sender ! , [ 'function nonce() view returns(address)' ] , provider ) ; op1 . nonce = await c . nonce ( ) . catch ( rethrow ( ) ) ; } if ( op1 . callGas == null && op . callData != null ) { if ( provider == null ) throw new Error ( 'must have entryPoint for callGas estimate' ) ; const gasEtimated = await provider . estimateGas ( { from : entryPoint ?. address , to : op1 . sender , data : op1 . callData } ) ; op1 . callGas = gasEtimated ; } if ( op1 . maxFeePerGas == null ) { if ( provider == null ) throw new Error ( 'must have entryPoint to autofill maxFeePerGas' ) ; const block = await provider . getBlock ( 'latest' ) ; op1 . maxFeePerGas = block . baseFeePerGas ! . add ( op1 . maxPriorityFeePerGas ?? DefaultsForUserOp . maxPriorityFeePerGas ) ; } if ( op1 . maxPriorityFeePerGas == undefined ) { op1 . maxPriorityFeePerGas = DefaultsForUserOp . maxPriorityFeePerGas ; } let op2 = fillUserOp ( op1 ) ; if ( op2 . preVerificationGas . toString ( ) == '0' ) { op2 . preVerificationGas = callDataCost ( packUserOp ( op2 , false ) ) ; } const chainId = await provider ! . getNetwork ( ) . then ( net => net . chainId ) ; const message = arrayify ( getRequestId ( op2 , entryPoint ! . address , chainId ) ) ; return { ... op2 , signature : await signer . signMessage ( message ) } ; }