Skip to content

Commit

Permalink
Add support for custom nonce space
Browse files Browse the repository at this point in the history
  • Loading branch information
Agusx1211 committed Aug 25, 2023
1 parent f8da5a4 commit 2b16ffb
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 12 deletions.
14 changes: 10 additions & 4 deletions packages/account/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,13 +613,16 @@ export class Account {
async signTransactions(
txs: commons.transaction.Transactionish,
chainId: ethers.BigNumberish,
pstatus?: AccountStatus
pstatus?: AccountStatus,
options?: {
nonceSpace?: ethers.BigNumberish
}
): Promise<commons.transaction.SignedTransactionBundle> {
const status = pstatus || (await this.status(chainId))
this.mustBeFullyMigrated(status)

const wallet = this.walletForStatus(chainId, status)
const signed = await wallet.signTransactions(txs)
const signed = await wallet.signTransactions(txs, options?.nonceSpace && { space: options?.nonceSpace })

return {
...signed,
Expand Down Expand Up @@ -793,12 +796,15 @@ export class Account {
chainId: ethers.BigNumberish,
quote?: FeeQuote,
skipPreDecorate: boolean = false,
callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void
callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void,
options?: {
nonceSpace?: ethers.BigNumberish
}
): Promise<ethers.providers.TransactionResponse> {
const status = await this.status(chainId)

const predecorated = skipPreDecorate ? txs : await this.predecorateTransactions(txs, status, chainId)
const signed = await this.signTransactions(predecorated, chainId)
const signed = await this.signTransactions(predecorated, chainId, undefined, options)
// TODO: is it safe to pass status again here?
return this.sendSignedTransactions(signed, chainId, quote, undefined, callback)
}
Expand Down
8 changes: 7 additions & 1 deletion packages/account/src/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FeeOption, proto } from "@0xsequence/relayer"
import { isDeferrable } from "./utils"

export type AccountSignerOptions = {
nonceSpace?: ethers.BigNumberish,
cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw',
stubSignatureOverrides?: Map<string, string>,
selectFee?: (
Expand Down Expand Up @@ -148,7 +149,12 @@ export class AccountSigner implements ethers.Signer {
return this.account.sendTransaction(
finalTransactions,
this.chainId,
prepare.feeQuote
prepare.feeQuote,
undefined,
undefined,
this.options?.nonceSpace ? {
nonceSpace: this.options.nonceSpace
} : undefined
)
}

Expand Down
110 changes: 110 additions & 0 deletions packages/account/tests/signer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,116 @@ describe('Account signer', () => {
})
})
})

describe('multiple nonce spaces', async () => {
it('should send transactions on multiple nonce spaces at once', async () => {
const signer1 = account.getSigner(chainId, { nonceSpace: '0x01' })
const signer2 = account.getSigner(chainId, { nonceSpace: 2 })
const signer3 = account.getSigner(chainId, { nonceSpace: ethers.BigNumber.from(ethers.utils.hexlify(ethers.utils.randomBytes(20))) })
const signer4 = account.getSigner(chainId, { nonceSpace: '0x04' })
const signer5 = account.getSigner(chainId, { nonceSpace: '0xffffffffffffffffffffffffffffffffffffffff' })

const results = await Promise.all([
signer1.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer2.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer3.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer4.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer5.sendTransaction({
to: ethers.Wallet.createRandom().address,
})
])

expect(results).to.have.lengthOf(5)
expect(results[0]).to.exist
expect(results[0].hash).to.exist
expect(results[1]).to.exist
expect(results[1].hash).to.exist
expect(results[2]).to.exist
expect(results[2].hash).to.exist
expect(results[3]).to.exist
expect(results[3].hash).to.exist
expect(results[4]).to.exist
expect(results[4].hash).to.exist

// hashes should be different
for (let i = 0; i < results.length; i++) {
for (let j = i + 1; j < results.length; j++) {
expect(results[i].hash).to.not.equal(results[j].hash)
}
}
})

it('should send multiple transactions on multiple nonce spaces at once', async () => {
const signer1 = account.getSigner(chainId, { nonceSpace: '0x01' })
const signer2 = account.getSigner(chainId, { nonceSpace: 2 })
const signer3 = account.getSigner(chainId, { nonceSpace: ethers.BigNumber.from(ethers.utils.hexlify(ethers.utils.randomBytes(20))) })
const signer4 = account.getSigner(chainId, { nonceSpace: '0x04' })
const signer5 = account.getSigner(chainId, { nonceSpace: '0xffffffffffffffffffffffffffffffffffffffff' })

await Promise.all([
signer1.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer2.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer3.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer4.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer5.sendTransaction({
to: ethers.Wallet.createRandom().address,
})
])

const results = await Promise.all([
signer1.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer2.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer3.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer4.sendTransaction({
to: ethers.Wallet.createRandom().address,
}),
signer5.sendTransaction({
to: ethers.Wallet.createRandom().address,
})
])

expect(results).to.have.lengthOf(5)
expect(results[0]).to.exist
expect(results[0].hash).to.exist
expect(results[1]).to.exist
expect(results[1].hash).to.exist
expect(results[2]).to.exist
expect(results[2].hash).to.exist
expect(results[3]).to.exist
expect(results[3].hash).to.exist
expect(results[4]).to.exist
expect(results[4].hash).to.exist

// hashes should be different
for (let i = 0; i < results.length; i++) {
for (let j = i + 1; j < results.length; j++) {
expect(results[i].hash).to.not.equal(results[j].hash)
}
}
})
})
})
})
})
27 changes: 20 additions & 7 deletions packages/wallet/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,27 @@ export class Wallet<
return this.signTransactions(bundle.transactions, bundle.nonce)
}

async nonceFor(
nonce?: ethers.BigNumberish | { space: ethers.BigNumberish }
): Promise<ethers.BigNumberish> {
let spaceValue

if (nonce && (nonce as any).space) {
spaceValue = ethers.BigNumber.from((nonce as any).space)
} else if (nonce === undefined) {
spaceValue = 0
} else {
return nonce as ethers.BigNumberish
}

const resultNonce = await this.reader().nonce(this.address, spaceValue)
if (resultNonce === undefined) throw new Error('Unable to determine nonce')
return commons.transaction.encodeNonce(spaceValue, resultNonce)
}

async signTransactions(
txs: Deferrable<commons.transaction.Transactionish>,
nonce?: ethers.BigNumberish
nonce?: ethers.BigNumberish | { space: ethers.BigNumberish }
): Promise<commons.transaction.SignedTransactionBundle> {
const transaction = await resolveArrayProperties<commons.transaction.Transactionish>(txs)
const transactions = commons.transaction.fromTransactionish(this.address, transaction)
Expand All @@ -297,12 +315,7 @@ export class Wallet<
})
}

let defaultedNonce = nonce
if (defaultedNonce === undefined) {
defaultedNonce = await this.reader().nonce(this.address, 0)
if (defaultedNonce === undefined) throw new Error('Unable to determine nonce')
}

const defaultedNonce = await this.nonceFor(nonce)
const digest = commons.transaction.digestOfTransactions(defaultedNonce, transactions)
const signature = await this.signDigest(digest, { transactions })

Expand Down

0 comments on commit 2b16ffb

Please sign in to comment.