Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes/erc 4337 #502

Merged
merged 3 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions src/controllers/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@ import { NetworkDescriptor, NetworkId } from '../../interfaces/networkDescriptor
import { Storage } from '../../interfaces/storage'
import { Message, UserRequest } from '../../interfaces/userRequest'
import { isSmartAccount } from '../../libs/account/account'
import {
AccountOp,
AccountOpStatus,
Call as AccountOpCall,
getSignableCalls
} from '../../libs/accountOp/accountOp'
import { AccountOp, AccountOpStatus, getSignableCalls } from '../../libs/accountOp/accountOp'
import { Call as AccountOpCall } from '../../libs/accountOp/types'
import { getAccountState } from '../../libs/accountState/accountState'
import {
getAccountOpBannersForEOA,
Expand Down
3 changes: 2 additions & 1 deletion src/controllers/signAccountOp/signAccountOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ export class SignAccountOpController extends EventEmitter {
userOperation,
this.accountOp.gasFeePayment.inToken
)

if (usesPaymaster) {
this.#addFeePayment()
} else {
Expand Down Expand Up @@ -722,7 +723,7 @@ export class SignAccountOpController extends EventEmitter {
'POST',
{
// send without the requestType prop
userOperation: (({ requestType, ...o }) => o)(userOperation),
userOperation: (({ requestType, activatorCall, ...o }) => o)(userOperation),
paymaster: AMBIRE_PAYMASTER
}
)
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/keystore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Transaction } from 'ethers'

import { HD_PATH_TEMPLATE_TYPE } from '../consts/derivation'
import { Call, GasFeePayment } from '../libs/accountOp/accountOp'
import { GasFeePayment } from '../libs/accountOp/accountOp'
import { Call } from '../libs/accountOp/types'
import { getHdPathFromTemplate } from '../utils/hdPath'
import { Account } from './account'
import { NetworkDescriptor } from './networkDescriptor'
Expand Down
3 changes: 2 additions & 1 deletion src/libs/accountOp/accountOp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { ethers } from 'ethers'

import { describe, expect, test } from '@jest/globals'

import { AccountOp, accountOpSignableHash, Call } from './accountOp'
import { AccountOp, accountOpSignableHash } from './accountOp'
import { Call } from './types'

describe('AccountOp', () => {
test('should generate a valid hash for signing', async () => {
Expand Down
13 changes: 3 additions & 10 deletions src/libs/accountOp/accountOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,7 @@ import { networks } from '../../consts/networks'
import { NetworkDescriptor, NetworkId } from '../../interfaces/networkDescriptor'
import { stringify } from '../bigintJson/bigintJson'
import { UserOperation } from '../userOperation/types'

export interface Call {
to: string
value: bigint
data: string
// if this call is associated with a particular user request
// multiple calls can be associated with the same user request, for example
// when a batching request is made
fromUserRequestId?: number
}
import { Call } from './types'

// This is an abstract representation of the gas fee payment
// 1) it cannot contain details about maxFeePerGas/baseFee because some networks might not be aware of EIP-1559; it only cares about total amount
Expand Down Expand Up @@ -124,6 +115,8 @@ export function isAccountOpsIntentEqual(

export function getSignableCalls(op: AccountOp) {
const callsToSign = op.calls.map((call: Call) => callToTuple(call))
if (op.asUserOperation && op.asUserOperation.activatorCall)
callsToSign.push(callToTuple(op.asUserOperation.activatorCall))
if (op.feeCall) callsToSign.push(callToTuple(op.feeCall))
return callsToSign
}
Expand Down
9 changes: 9 additions & 0 deletions src/libs/accountOp/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface Call {
to: string
value: bigint
data: string
// if this call is associated with a particular user request
// multiple calls can be associated with the same user request, for example
// when a batching request is made
fromUserRequestId?: number
}
13 changes: 11 additions & 2 deletions src/libs/estimate/estimate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SPOOF_SIGTYPE } from '../../consts/signatures'
import { Account, AccountOnchainState } from '../../interfaces/account'
import { NetworkDescriptor } from '../../interfaces/networkDescriptor'
import { getAccountDeployParams } from '../account/account'
import { AccountOp } from '../accountOp/accountOp'
import { AccountOp, getSignableCalls } from '../accountOp/accountOp'
import { fromDescriptor } from '../deployless/deployless'
import { getProbableCallData } from '../gasPrice/gasPrice'
import {
Expand Down Expand Up @@ -156,10 +156,20 @@ export async function estimate(
const is4337Broadcast = opts && opts.is4337Broadcast
const usesOneTimeNonce =
opts && opts.is4337Broadcast && shouldUseOneTimeNonce(op.asUserOperation!)
const IAmbireAccount = new Interface(AmbireAccount.abi)
if (is4337Broadcast) {
// using Object.assign as typescript doesn't work otherwise
const userOp = Object.assign({}, op.asUserOperation)
userOp!.paymasterAndData = getPaymasterSpoof()

// add the activatorCall to the estimation
if (userOp.activatorCall) {
const spoofSig = abiCoder.encode(['address'], [account.associatedKeys[0]]) + SPOOF_SIGTYPE
userOp.callData = IAmbireAccount.encodeFunctionData('executeMultiple', [
[[getSignableCalls(op), spoofSig]]
])
}

const deployless4337Estimator = fromDescriptor(
provider,
Estimation4337,
Expand Down Expand Up @@ -224,7 +234,6 @@ export async function estimate(
: deployment.gasUsed + accountOpToExecuteBefore.gasUsed + accountOp.gasUsed

if (opts?.calculateRefund) {
const IAmbireAccount = new Interface(AmbireAccount.abi)
const IAmbireAccountFactory = new Interface(AmbireAccountFactory.abi)

const accountCalldata = op.accountOpToExecuteBefore
Expand Down
3 changes: 2 additions & 1 deletion src/libs/humanizer/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { NetworkId } from 'interfaces/networkDescriptor'

import { Account } from '../../interfaces/account'
import { Message, TypedMessage } from '../../interfaces/userRequest'
import { AccountOp, Call } from '../accountOp/accountOp'
import { AccountOp } from '../accountOp/accountOp'
import { Call } from '../accountOp/types'

export type HumanizerVisualization = {
type: 'token' | 'address' | 'label' | 'action' | 'nft' | 'danger'
Expand Down
3 changes: 3 additions & 0 deletions src/libs/userOperation/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Call } from '../accountOp/types'

export type UserOpRequestType = 'standard' | 'activator' | 'recovery'

export interface UserOperation {
Expand All @@ -14,4 +16,5 @@ export interface UserOperation {
signature: string // hex string
// https://github.com/AmbireTech/ambire-app/wiki/Ambire-Flows-(wrap,-sign,-payment,-broadcast)#erc-4337-edge-case
requestType: UserOpRequestType
activatorCall?: Call
}
14 changes: 10 additions & 4 deletions src/libs/userOperation/userOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
} from '../../consts/deploy'
import { SPOOF_SIGTYPE } from '../../consts/signatures'
import { Account, AccountOnchainState } from '../../interfaces/account'
import { AccountOp, getSignableCalls } from '../accountOp/accountOp'
import { AccountOp, callToTuple, getSignableCalls } from '../accountOp/accountOp'
import { Call } from '../accountOp/types'
import { UserOperation } from './types'

export function calculateCallDataCost(callData: string): bigint {
Expand Down Expand Up @@ -60,16 +61,17 @@ export function toUserOperation(

// give permissions to the entry if there aren't nay
const ambireAccount = new ethers.BaseContract(accountOp.accountAddr, AmbireAccount.abi)
let activatorCall: Call | null = null
if (!accountState.isErc4337Enabled) {
const givePermsToEntryPointData = ambireAccount.interface.encodeFunctionData(
'setAddrPrivilege',
[ERC_4337_ENTRYPOINT, ENTRY_POINT_MARKER]
)
accountOp.calls.push({
activatorCall = {
to: accountOp.accountAddr,
value: 0n,
data: givePermsToEntryPointData
})
}

requestType = 'activator'
}
Expand Down Expand Up @@ -110,12 +112,16 @@ export function toUserOperation(
}

const abiCoder = new ethers.AbiCoder()
const packed = abiCoder.encode(
let packed = abiCoder.encode(
[
'tuple(address, uint256, bytes, bytes, uint256, uint256, uint256, uint256, uint256, bytes, bytes)'
],
[Object.values(userOperation)]
)
if (activatorCall) {
userOperation.activatorCall = activatorCall
packed += abiCoder.encode(['address', 'uint256', 'bytes'], callToTuple(activatorCall))
}
userOperation.preVerificationGas = ethers.toBeHex(21000n + calculateCallDataCost(packed))
userOperation.paymasterAndData = '0x'
userOperation.signature = '0x'
Expand Down
2 changes: 1 addition & 1 deletion src/services/bundlers/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class Bundler {
const provider = new StaticJsonRpcProvider(url)

return provider.send('eth_sendUserOperation', [
(({ requestType, ...o }) => o)(userOperation),
(({ requestType, activatorCall, ...o }) => o)(userOperation),
ERC_4337_ENTRYPOINT
])
}
Expand Down