Skip to content

Commit 0478098

Browse files
committed
Migrations work with v3 wallet
1 parent 47c5fd4 commit 0478098

20 files changed

Lines changed: 914 additions & 534 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export * as migration from './migrations/index.js'
2-
export * as migrator from './migrator.js'
2+
export * from './types.js'

packages/utils/migration/src/migrations/index.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { State } from '@0xsequence/wallet-core'
2+
import { Payload } from '@0xsequence/wallet-primitives'
13
import { Address, Hex } from 'ox'
2-
import { UnsignedMigration, VersionedContext } from '../migrator.js'
3-
import { Migration_v1v3 } from './v1/migration_v1_v3.js'
4+
import { UnsignedMigration, VersionedContext } from '../types.js'
5+
import { MigrationEncoder_v1v3 } from './v1/migration_v1_v3.js'
46

5-
export interface Migration<FromConfigType, ToConfigType, ConvertOptionsType> {
7+
export interface MigrationEncoder<FromConfigType, ToConfigType, ConvertOptionsType, PrepareOptionsType> {
68
fromVersion: number
79
toVersion: number
810

@@ -19,20 +21,32 @@ export interface Migration<FromConfigType, ToConfigType, ConvertOptionsType> {
1921
* @param walletAddress The wallet address to prepare the migration for
2022
* @param contexts The contexts to prepare the migration for
2123
* @param toConfig The configuration to prepare the migration for
22-
* @returns The prepared migration
24+
* @param options The prepare options
25+
* @returns The migration payload to be signed
2326
*/
2427
prepareMigration: (
2528
walletAddress: Address.Address,
2629
contexts: VersionedContext,
2730
toConfig: ToConfigType,
31+
options: PrepareOptionsType,
2832
) => Promise<UnsignedMigration>
2933

3034
/**
31-
* Decodes the transactions from a migration
32-
* @param transactions The transactions to decode
33-
* @returns The decoded address and resulting image hash for the migration transactions
35+
* Encodes the a transaction for a given migration
36+
* @param migration The migration to encode the transaction for
37+
* @returns The encoded transaction
3438
*/
35-
decodeTransactions: (transactions: UnsignedMigration['transactions']) => Promise<{
39+
toTransactionData: (migration: State.Migration) => Promise<{
40+
to: Address.Address
41+
data: Hex.Hex
42+
}>
43+
44+
/**
45+
* Decodes the payload from a migration
46+
* @param payload The payload to decode
47+
* @returns The decoded address and resulting image hash for the migration payload
48+
*/
49+
decodePayload: (payload: Payload.Calls) => Promise<{
3650
address: Address.Address
3751
toImageHash: Hex.Hex
3852
}>
@@ -45,4 +59,15 @@ export interface Migrator<FromWallet, ToWallet, ConvertOptionsType> {
4559
convertWallet: (fromWallet: FromWallet, options: ConvertOptionsType) => Promise<ToWallet>
4660
}
4761

48-
export const v1v3 = new Migration_v1v3()
62+
export const encoders: MigrationEncoder<any, any, any, any>[] = [new MigrationEncoder_v1v3()]
63+
64+
export function getMigrationEncoder<FromConfigType, ToConfigType, ConvertOptionsType, PrepareOptionsType>(
65+
fromVersion: number,
66+
toVersion: number,
67+
): MigrationEncoder<FromConfigType, ToConfigType, ConvertOptionsType, PrepareOptionsType> {
68+
const encoder = encoders.find((encoder) => encoder.fromVersion === fromVersion && encoder.toVersion === toVersion)
69+
if (!encoder) {
70+
throw new Error(`Unsupported from version: ${fromVersion} to version: ${toVersion}`)
71+
}
72+
return encoder
73+
}

packages/utils/migration/src/migrations/v1/migration_v1_v3.ts

Lines changed: 78 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { v1, commons as v2commons } from '@0xsequence/v2core'
2-
import { WalletV1 } from '@0xsequence/v2wallet'
3-
import { Config as V3Config, Context as V3Context, Extensions as V3Extensions } from '@0xsequence/wallet-primitives'
2+
import { State } from '@0xsequence/wallet-core'
3+
import {
4+
Payload,
5+
Config as V3Config,
6+
Context as V3Context,
7+
Extensions as V3Extensions,
8+
} from '@0xsequence/wallet-primitives'
49
import { AbiFunction, Address, Hex } from 'ox'
5-
import { SignedMigration, UnsignedMigration, VersionedContext } from '../../migrator.js'
6-
import { Migration } from '../index.js'
10+
import { UnsignedMigration, VersionedContext } from '../../types.js'
11+
import { MigrationEncoder } from '../index.js'
712
import { createDefaultV3Topology } from '../v3/config.js'
813

914
// uint160(keccak256("org.sequence.sdk.migration.v1v3.space.nonce"))
@@ -17,25 +22,31 @@ export type ConvertOptions = {
1722
extensions?: V3Extensions.Extensions
1823
}
1924

20-
export class Migration_v1v3 implements Migration<v1.config.WalletConfig, V3Config.Config, ConvertOptions> {
25+
export type PrepareOptions = {
26+
space?: bigint
27+
}
28+
29+
export class MigrationEncoder_v1v3
30+
implements MigrationEncoder<v1.config.WalletConfig, V3Config.Config, ConvertOptions, PrepareOptions>
31+
{
2132
fromVersion = 1
2233
toVersion = 3
2334

24-
async convertConfig(v1Config: v1.config.WalletConfig, options: ConvertOptions): Promise<V3Config.Config> {
25-
const signerLeaves: V3Config.SignerLeaf[] = v1Config.signers.map((signer) => ({
35+
async convertConfig(fromConfig: v1.config.WalletConfig, options: ConvertOptions): Promise<V3Config.Config> {
36+
const signerLeaves: V3Config.SignerLeaf[] = fromConfig.signers.map((signer) => ({
2637
type: 'signer',
2738
address: Address.from(signer.address),
2839
weight: BigInt(signer.weight),
2940
}))
3041
const v1NestedTopology = V3Config.flatLeavesToTopology(signerLeaves)
31-
const v3Config: V3Config.Config = {
42+
return {
3243
threshold: 1n,
3344
checkpoint: 0n,
3445
topology: [
3546
{
3647
type: 'nested',
3748
weight: 1n,
38-
threshold: BigInt(v1Config.threshold),
49+
threshold: BigInt(fromConfig.threshold),
3950
tree: v1NestedTopology,
4051
},
4152
{
@@ -46,80 +57,101 @@ export class Migration_v1v3 implements Migration<v1.config.WalletConfig, V3Confi
4657
},
4758
],
4859
}
49-
return v3Config
5060
}
5161

5262
async prepareMigration(
5363
walletAddress: Address.Address,
5464
contexts: VersionedContext,
5565
toConfig: V3Config.Config,
66+
options: PrepareOptions,
5667
): Promise<UnsignedMigration> {
5768
const v3Context = contexts[3] || V3Context.Rc3
5869
if (!V3Context.isContext(v3Context)) {
5970
throw new Error('Invalid context')
6071
}
6172

62-
const nonce = v2commons.transaction.encodeNonce(MIGRATION_V1_V3_NONCE_SPACE, 0)
73+
const space = options?.space ?? BigInt(MIGRATION_V1_V3_NONCE_SPACE)
74+
const nonce = 0n // Nonce must be unused
75+
// const v2Nonce = v2commons.transaction.encodeNonce(space, nonce)
6376

6477
// Update implementation to v3
6578
const updateImplementationAbi = AbiFunction.from('function updateImplementation(address implementation)')
66-
const updateImplementationTx = {
79+
const updateImplementationTx: Payload.Call = {
6780
to: walletAddress,
6881
data: AbiFunction.encodeData(updateImplementationAbi, [v3Context.stage2]),
82+
value: 0n,
83+
gasLimit: 0n,
84+
delegateCall: false,
85+
onlyFallback: false,
86+
behaviorOnError: 'revert',
6987
}
7088
// Update configuration to v3
71-
const v3ImageHash = Hex.fromBytes(V3Config.hashConfiguration(toConfig))
89+
const toImageHash = Hex.fromBytes(V3Config.hashConfiguration(toConfig))
7290
const updateImageHashAbi = AbiFunction.from('function updateImageHash(bytes32 imageHash)')
73-
const updateImageHashTx = {
91+
const updateImageHashTx: Payload.Call = {
7492
to: walletAddress,
75-
data: AbiFunction.encodeData(updateImageHashAbi, [v3ImageHash]),
93+
data: AbiFunction.encodeData(updateImageHashAbi, [toImageHash]),
94+
value: 0n,
95+
gasLimit: 0n,
96+
delegateCall: false,
97+
onlyFallback: false,
98+
behaviorOnError: 'revert',
99+
}
100+
101+
const payload: Payload.Calls = {
102+
type: 'call',
103+
space,
104+
nonce,
105+
calls: [updateImplementationTx, updateImageHashTx],
76106
}
77107

78108
return {
79-
transactions: [updateImplementationTx, updateImageHashTx],
109+
payload,
80110
fromVersion: this.fromVersion,
81111
toVersion: this.toVersion,
82-
nonce,
112+
toConfig,
83113
}
84114
}
85115

86-
/**
87-
* Signs a migration with a wallet
88-
* @notice V1 Wallets must call this method for each chain they are migrating on
89-
* @param migration The unsigned migration to sign
90-
* @param wallet The wallet to sign the migration with
91-
* @returns The signed migration
92-
*/
93-
//FIXME Remove this function. Signing is not a responsibility of the migration class.
94-
async signMigration(migration: UnsignedMigration, wallet: WalletV1): Promise<SignedMigration> {
95-
const { address } = await this.decodeTransactions(migration.transactions)
96-
if (address !== wallet.address) {
97-
throw new Error('Wallet address does not match migration address')
116+
async toTransactionData(migration: State.Migration): Promise<{ to: Address.Address; data: Hex.Hex }> {
117+
const { payload, signature, chainId } = migration
118+
const walletAddress = payload.calls[0]!.to
119+
const v2Nonce = v2commons.transaction.encodeNonce(payload.space, payload.nonce)
120+
const transactions = payload.calls.map((tx) => ({
121+
to: tx.to,
122+
data: tx.data,
123+
gasLimit: tx.gasLimit,
124+
revertOnError: tx.behaviorOnError === 'revert',
125+
}))
126+
const digest = v2commons.transaction.digestOfTransactions(v2Nonce, transactions)
127+
const txBundle: v2commons.transaction.SignedTransactionBundle = {
128+
entrypoint: walletAddress,
129+
transactions,
130+
nonce: v2Nonce,
131+
chainId,
132+
signature,
133+
intent: {
134+
id: digest,
135+
wallet: walletAddress,
136+
},
98137
}
99-
const txBundle: v2commons.transaction.TransactionBundle = {
100-
entrypoint: wallet.address,
101-
transactions: migration.transactions.map((tx) => ({
102-
to: tx.to,
103-
data: tx.data,
104-
gasLimit: 0n,
105-
revertOnError: true,
106-
})),
107-
nonce: migration.nonce,
138+
const encodedData = v2commons.transaction.encodeBundleExecData(txBundle)
139+
Hex.assert(encodedData)
140+
return {
141+
to: walletAddress,
142+
data: encodedData,
108143
}
109-
const { signature } = await wallet.signTransactionBundle(txBundle)
110-
Hex.assert(signature)
111-
return { ...migration, signature }
112144
}
113145

114-
async decodeTransactions(transactions: UnsignedMigration['transactions']): Promise<{
146+
async decodePayload(payload: Payload.Calls): Promise<{
115147
address: Address.Address
116148
toImageHash: Hex.Hex
117149
}> {
118-
if (transactions.length !== 2) {
119-
throw new Error('Invalid transactions')
150+
if (payload.calls.length !== 2) {
151+
throw new Error('Invalid calls')
120152
}
121-
const tx1 = transactions[0]!
122-
const tx2 = transactions[1]!
153+
const tx1 = payload.calls[0]!
154+
const tx2 = payload.calls[1]!
123155
if (tx1.to !== tx2.to) {
124156
throw new Error('Invalid to address')
125157
}

0 commit comments

Comments
 (0)