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

feat: 4337 integration #693

Merged
merged 29 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
67192ab
Initial commit
yagopv Feb 8, 2024
df96683
Add Safe4337Pack
yagopv Feb 8, 2024
08febc3
Add signing
yagopv Feb 9, 2024
dccaa25
Add submitUserOp
yagopv Feb 9, 2024
a783a33
Add some calcs
yagopv Feb 9, 2024
6f4fae3
Add getNonce from entrypoint
yagopv Feb 9, 2024
6b7b349
add estimateUserOperation
DaniSomoza Feb 9, 2024
05f7355
using UserOperation instead of SafeUserOperation
DaniSomoza Feb 9, 2024
8e3970e
Add wrap useroperation encode
yagopv Feb 14, 2024
f5f9001
paymaster optional
yagopv Feb 14, 2024
2ebbf30
Add script
yagopv Feb 14, 2024
7148aa3
Add convert to hex
yagopv Feb 14, 2024
aa75c09
Upload changes to execute the 1USDC transfer
yagopv Feb 14, 2024
115d9f7
Add Call to all the transactions
yagopv Feb 15, 2024
0259107
Add feedata
yagopv Feb 15, 2024
18fbe21
Add batch transactions support for 4337
DaniSomoza Feb 16, 2024
b5b0fda
feat: Initialize new safe (`initCode`) (#707)
yagopv Mar 1, 2024
5a871f2
feat(relay-kit): Improve relay kit base pack (#726)
yagopv Mar 11, 2024
083c3b7
Added paymaster feature (#721)
DaniSomoza Mar 15, 2024
d4a1bee
feat(relay-kit): Add support for time-range dates in the Safe4337Pack…
yagopv Mar 20, 2024
c65f94e
Merge branch 'development' of https://github.com/safe-global/safe-cor…
yagopv Mar 20, 2024
921fae1
feat(relay-kit): getEstimateFee inside createTransaction in the 4337 …
DaniSomoza Mar 22, 2024
924ae56
Set alpha version 0
dasanra Mar 22, 2024
96e63c7
feat: Add new safe-4337 pack tests (#741)
yagopv Apr 3, 2024
7e31b96
chore: make method private (#754)
yagopv Apr 5, 2024
7f4d95c
Merge branch 'development' into feat/4337-pack
dasanra Apr 5, 2024
8887901
Merge branch 'development' into feat/4337-pack
dasanra Apr 8, 2024
75f678e
Fix test
yagopv Apr 10, 2024
9f4e594
Merge branch 'development' into feat/4337-pack
dasanra Apr 15, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
run: yarn build

- name: Test
env:
PRIVATE_KEY: ${{ secrets.TESTING_PRIVATE_KEY }}
run: yarn test

- name: Upload coverage report
Expand Down
6 changes: 3 additions & 3 deletions packages/account-abstraction-kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@safe-global/account-abstraction-kit-poc",
"version": "2.0.3",
"version": "2.1.0-alpha.0",
"description": "Safe Account Abstraction Kit PoC",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
Expand Down Expand Up @@ -34,8 +34,8 @@
"access": "public"
},
"dependencies": {
"@safe-global/protocol-kit": "^3.0.2",
"@safe-global/relay-kit": "^2.0.3",
"@safe-global/protocol-kit": "^3.1.0-alpha.0",
"@safe-global/relay-kit": "^2.1.0-alpha.0",
"@safe-global/safe-core-sdk-types": "^4.0.2"
}
}
33 changes: 16 additions & 17 deletions packages/account-abstraction-kit/src/AccountAbstraction.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Safe, { predictSafeAddress } from '@safe-global/protocol-kit'
import { GelatoRelayPack, RelayKitBasePack } from '@safe-global/relay-kit'
import { GelatoRelayPack } from '@safe-global/relay-kit'
import { EthAdapter, SafeTransaction } from '@safe-global/safe-core-sdk-types'
import AccountAbstraction from './AccountAbstraction'

Expand Down Expand Up @@ -153,11 +153,9 @@ describe('AccountAbstraction', () => {
const relayResponseMock = { taskId: '0xTaskID' }

it('should return the Gelato taskId of the relayed transaction', async () => {
GelatoRelayPackMock.prototype.createRelayedTransaction.mockResolvedValueOnce(safeTxMock)
GelatoRelayPackMock.prototype.createTransaction.mockResolvedValueOnce(safeTxMock)
safeInstanceMock.signTransaction.mockResolvedValueOnce(signedSafeTxMock)
GelatoRelayPackMock.prototype.executeRelayTransaction.mockResolvedValueOnce(
relayResponseMock
)
GelatoRelayPackMock.prototype.executeTransaction.mockResolvedValueOnce(relayResponseMock)
accountAbstraction.setRelayKit(
new GelatoRelayPack({ protocolKit: accountAbstraction.protocolKit })
)
Expand All @@ -166,20 +164,20 @@ describe('AccountAbstraction', () => {

expect(result).toBe(relayResponseMock)

expect(GelatoRelayPackMock.prototype.createRelayedTransaction).toHaveBeenCalledTimes(1)
expect(GelatoRelayPackMock.prototype.createRelayedTransaction).toHaveBeenCalledWith({
expect(GelatoRelayPackMock.prototype.createTransaction).toHaveBeenCalledTimes(1)
expect(GelatoRelayPackMock.prototype.createTransaction).toHaveBeenCalledWith({
transactions: transactionsMock,
options: optionsMock
})

expect(safeInstanceMock.signTransaction).toHaveBeenCalledTimes(1)
expect(safeInstanceMock.signTransaction).toHaveBeenCalledWith(safeTxMock)

expect(GelatoRelayPackMock.prototype.executeRelayTransaction).toHaveBeenCalledTimes(1)
expect(GelatoRelayPackMock.prototype.executeRelayTransaction).toHaveBeenCalledWith(
signedSafeTxMock,
optionsMock
)
expect(GelatoRelayPackMock.prototype.executeTransaction).toHaveBeenCalledTimes(1)
expect(GelatoRelayPackMock.prototype.executeTransaction).toHaveBeenCalledWith({
executable: signedSafeTxMock,
options: optionsMock
})
})

it('should throw if the protocol-kit is not initialized', async () => {
Expand All @@ -192,21 +190,22 @@ describe('AccountAbstraction', () => {
'protocolKit not initialized. Call init() first'
)

expect(GelatoRelayPackMock.prototype.createRelayedTransaction).not.toHaveBeenCalled()
expect(GelatoRelayPackMock.prototype.createTransaction).not.toHaveBeenCalled()
expect(safeInstanceMock.signTransaction).not.toHaveBeenCalled()
expect(GelatoRelayPackMock.prototype.executeRelayTransaction).not.toHaveBeenCalled()
expect(GelatoRelayPackMock.prototype.executeTransaction).not.toHaveBeenCalled()
})

it('should throw if relay-kit is not initialized', async () => {
accountAbstraction.setRelayKit(undefined as unknown as RelayKitBasePack)
const accountAbstraction = new AccountAbstraction(ethersAdapter as unknown as EthAdapter)
await accountAbstraction.init()

expect(accountAbstraction.relayTransaction(transactionsMock, optionsMock)).rejects.toThrow(
'relayKit not initialized. Call setRelayKit(pack) first'
)

expect(GelatoRelayPackMock.prototype.createRelayedTransaction).not.toHaveBeenCalled()
expect(GelatoRelayPackMock.prototype.createTransaction).not.toHaveBeenCalled()
expect(safeInstanceMock.signTransaction).not.toHaveBeenCalled()
expect(GelatoRelayPackMock.prototype.executeRelayTransaction).not.toHaveBeenCalled()
expect(GelatoRelayPackMock.prototype.executeTransaction).not.toHaveBeenCalled()
})
})
})
Expand Down
9 changes: 5 additions & 4 deletions packages/account-abstraction-kit/src/AccountAbstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { RelayKitBasePack } from '@safe-global/relay-kit'
import {
MetaTransactionData,
MetaTransactionOptions,
EthAdapter
EthAdapter,
SafeTransaction
} from '@safe-global/safe-core-sdk-types'

/**
Expand Down Expand Up @@ -91,14 +92,14 @@ class AccountAbstraction {
throw new Error('relayKit not initialized. Call setRelayKit(pack) first')
}

const relayedTransaction = await this.relayKit.createRelayedTransaction({
const relayedTransaction = (await this.relayKit.createTransaction({
transactions,
options
})
})) as SafeTransaction

const signedSafeTransaction = await this.protocolKit.signTransaction(relayedTransaction)

return await this.relayKit.executeRelayTransaction(signedSafeTransaction, options)
return this.relayKit.executeTransaction({ executable: signedSafeTransaction, options })
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/protocol-kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@safe-global/protocol-kit",
"version": "3.0.2",
"version": "3.1.0-alpha.0",
"description": "SDK to interact with Safe smart contracts",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
Expand Down
21 changes: 21 additions & 0 deletions packages/protocol-kit/src/Safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import {
encodeSetupCallData,
getChainSpecificDefaultSaltNonce,
getPredictedSafeAddressInitCode,
predictSafeAddress
} from './contracts/utils'
import { DEFAULT_SAFE_VERSION } from './contracts/config'
Expand Down Expand Up @@ -188,6 +189,26 @@ class Safe {
})
}

/**
* Returns the initialization code to deploy a Safe account based on the predicted address.
*
* @returns The Safe configuration
*/
async getInitCode(): Promise<string> {
if (!this.#predictedSafe) {
throw new Error('The Safe already exists')
}

const chainId = await this.#ethAdapter.getChainId()

return getPredictedSafeAddressInitCode({
ethAdapter: this.#ethAdapter,
chainId,
customContracts: this.#contractManager.contractNetworks?.[chainId.toString()],
...this.#predictedSafe
})
}

/**
* Returns the address of the current SafeProxy contract.
*
Expand Down
71 changes: 63 additions & 8 deletions packages/protocol-kit/src/contracts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ export interface encodeSetupCallDataProps {
export function encodeCreateProxyWithNonce(
safeProxyFactoryContract: SafeProxyFactoryContract,
safeSingletonAddress: string,
initializer: string
initializer: string,
salt?: string
) {
return safeProxyFactoryContract.encode('createProxyWithNonce', [
safeSingletonAddress,
initializer,
PREDETERMINED_SALT_NONCE
salt || PREDETERMINED_SALT_NONCE
])
}

Expand Down Expand Up @@ -183,6 +184,64 @@ export function getChainSpecificDefaultSaltNonce(chainId: bigint): string {
return `0x${Buffer.from(keccak_256(PREDETERMINED_SALT_NONCE + chainId)).toString('hex')}`
}

export async function getPredictedSafeAddressInitCode({
ethAdapter,
chainId,
safeAccountConfig,
safeDeploymentConfig = {},
isL1SafeSingleton = false,
customContracts
}: PredictSafeAddressProps): Promise<string> {
validateSafeAccountConfig(safeAccountConfig)
validateSafeDeploymentConfig(safeDeploymentConfig)

const {
safeVersion = DEFAULT_SAFE_VERSION,
saltNonce = getChainSpecificDefaultSaltNonce(chainId)
} = safeDeploymentConfig

const safeProxyFactoryContract = await memoizedGetProxyFactoryContract({
ethAdapter,
safeVersion,
customContracts,
chainId: chainId.toString()
})

const safeContract = await memoizedGetSafeContract({
ethAdapter,
safeVersion,
isL1SafeSingleton,
customContracts,
chainId: chainId.toString()
})

const initializer = await encodeSetupCallData({
ethAdapter,
safeAccountConfig,
safeContract,
customContracts,
customSafeVersion: safeVersion // it is more efficient if we provide the safeVersion manually
})

const encodedNonce = toBuffer(ethAdapter.encodeParameters(['uint256'], [saltNonce])).toString(
'hex'
)
const safeSingletonAddress = await safeContract.getAddress()
const initCodeCallData = encodeCreateProxyWithNonce(
safeProxyFactoryContract,
safeSingletonAddress,
initializer,
'0x' + encodedNonce
)
const safeProxyFactoryAddress = await safeProxyFactoryContract.getAddress()
const initCode = `0x${[safeProxyFactoryAddress, initCodeCallData].reduce(
(acc, x) => acc + x.replace('0x', ''),
''
)}`

return initCode
}

export async function predictSafeAddress({
ethAdapter,
chainId,
Expand Down Expand Up @@ -232,13 +291,10 @@ export async function predictSafeAddress({
const encodedNonce = toBuffer(ethAdapter.encodeParameters(['uint256'], [saltNonce])).toString(
'hex'
)

const salt = keccak256(
toBuffer('0x' + keccak256(toBuffer(initializer)).toString('hex') + encodedNonce)
)

const input = ethAdapter.encodeParameters(['address'], [await safeContract.getAddress()])

const from = await safeProxyFactoryContract.getAddress()

// On the zkSync Era chain, the counterfactual deployment address is calculated differently
Expand All @@ -250,13 +306,12 @@ export async function predictSafeAddress({
}

const constructorData = toBuffer(input).toString('hex')

const initCode = proxyCreationCode + constructorData

const proxyAddress =
'0x' + generateAddress2(toBuffer(from), toBuffer(salt), toBuffer(initCode)).toString('hex')
const predictedAddress = ethAdapter.getChecksummedAddress(proxyAddress)

return ethAdapter.getChecksummedAddress(proxyAddress)
return predictedAddress
}

export const validateSafeAccountConfig = ({ owners, threshold }: SafeAccountConfig): void => {
Expand Down
4 changes: 3 additions & 1 deletion packages/protocol-kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
PREDETERMINED_SALT_NONCE,
encodeCreateProxyWithNonce,
encodeSetupCallData,
predictSafeAddress
predictSafeAddress,
getPredictedSafeAddressInitCode
} from './contracts/utils'
import ContractManager from './managers/contractManager'
import SafeFactory, { DeploySafeProps, SafeFactoryConfig } from './safeFactory'
Expand Down Expand Up @@ -159,6 +160,7 @@ export {
getSignMessageLibContract,
isGasTokenCompatibleWithHandlePayment,
predictSafeAddress,
getPredictedSafeAddressInitCode,
standardizeSafeTransactionData,
validateEip3770Address,
validateEthereumAddress,
Expand Down
1 change: 1 addition & 0 deletions packages/relay-kit/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PRIVATE_KEY=
3 changes: 2 additions & 1 deletion packages/relay-kit/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const config = {
'^@safe-global/protocol-kit/typechain/(.*)$': '<rootDir>/../protocol-kit/typechain/$1',
'^@safe-global/protocol-kit/(.*)$': '<rootDir>/../protocol-kit/src/$1',
'^@safe-global/relay-kit/(.*)$': '<rootDir>/src/$1'
}
},
testTimeout: 10000
}

module.exports = config
8 changes: 5 additions & 3 deletions packages/relay-kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@safe-global/relay-kit",
"version": "2.0.3",
"version": "2.1.0-alpha.0",
"description": "Safe Relay Kit",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
Expand Down Expand Up @@ -36,7 +36,9 @@
},
"dependencies": {
"@gelatonetwork/relay-sdk": "^5.5.0",
"@safe-global/protocol-kit": "^3.0.2",
"@safe-global/safe-core-sdk-types": "^4.0.2"
"@safe-global/protocol-kit": "^3.1.0-alpha.0",
"@safe-global/safe-core-sdk-types": "^4.0.2",
"@safe-global/safe-modules-deployments": "^2.0.0",
"ethers": "^6.7.1"
}
}
Loading
Loading