Skip to content

Commit

Permalink
feat: parse extraArgs
Browse files Browse the repository at this point in the history
On `parseBytes` command, both standalone and deep inside e.g.
`Router.ccipSend` function data parsing
  • Loading branch information
andrevmatos committed Nov 27, 2024
1 parent 8dac065 commit c55978f
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Allow `parseBytes` command to parse EVMExtraArgs bytearrays, both standalone and in structs (#7)

## [0.1.2] - 2024-11-25
- Add public `recursiveParseErrors` function to lib, to return nested/inner ABI errors
- Use it everywhere: `parseBytes` command, thrown exceptions, and `prettyReceipt` output of failed execs
Expand Down
20 changes: 19 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
getSomeBlockNumberBefore,
getTypeAndVersion,
lazyCached,
parseExtraArgs,
parseWithFragment,
recursiveParseError,
} from './lib/index.js'
Expand Down Expand Up @@ -581,6 +582,12 @@ export function parseBytes({ data, selector }: { data: string; selector?: string
parsed = parseWithFragment(selector, data)
} else {
if (isBytesLike(data)) {
const extraArgs = parseExtraArgs(data)
if (extraArgs) {
const { _tag, ...rest } = extraArgs
console.info(`${_tag}:`, rest)
return
}
parsed = parseWithFragment(dataSlice(data, 0, 4), dataSlice(data, 4))
}
if (!parsed) {
Expand All @@ -592,7 +599,18 @@ export function parseBytes({ data, selector }: { data: string; selector?: string
const name = fragment.constructor.name.replace(/Fragment$/, '')
console.info(`${name}: ${contract.replace(/_\d\.\d.*$/, '')} ${fragment.format('full')}`)
if (args) {
const formatted = formatResult(args)
const formatted = formatResult(args, (val, key) => {
if (key === 'extraArgs' && isHexString(val)) {
const extraArgs = parseExtraArgs(val)
if (extraArgs) {
const { _tag, ...rest } = extraArgs
return `${_tag}(${Object.entries(rest)
.map(([k, v]) => `${k}=${v}`)
.join(', ')})`
}
}
return val
})
const ps: unknown[] = []
if (fragment.name === 'ReceiverError' && args.err === '0x') {
ps.push('[possibly out-of-gas or abi.decode error]')
Expand Down
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export {
ExecutionState,
defaultAbiCoder,
encodeExtraArgs,
parseExtraArgs,
} from './types.js'
export {
bigIntReplacer,
Expand Down
28 changes: 27 additions & 1 deletion src/lib/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { dataSlice, getNumber } from 'ethers'
import { encodeExtraArgs } from './types.js'
import { encodeExtraArgs, parseExtraArgs } from './types.js'

describe('encodeExtraArgs', () => {
it('should encode v2 args', () => {
Expand All @@ -19,3 +19,29 @@ describe('encodeExtraArgs', () => {
expect(encodeExtraArgs({})).toBe('0x')
})
})

describe('parseExtraArgs', () => {
it('should parse v1 args', () => {
const res = parseExtraArgs(
'0x97a657c9000000000000000000000000000000000000000000000000000000000000000a',
)
expect(res).toEqual({ _tag: 'EVMExtraArgsV1', gasLimit: 10n })
})

it('should parse v2 args', () => {
const res = parseExtraArgs(
'0x181dcf10000000000000000000000000000000000000000000000000000000000000000b0000000000000000000000000000000000000000000000000000000000000001',
)
expect(res).toEqual({ _tag: 'EVMExtraArgsV2', gasLimit: 11n, allowOutOfOrderExecution: true })
})

it('should return v1 on empty data', () => {
const res = parseExtraArgs('0x')
expect(res).toEqual({ _tag: 'EVMExtraArgsV1' })
})

it('should return undefined on unknown data', () => {
const res = parseExtraArgs('0x1234')
expect(res).toBeUndefined()
})
})
27 changes: 24 additions & 3 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type SolidityTuple,
parseAbi,
} from 'abitype'
import { type Log, AbiCoder, concat, dataSlice, id } from 'ethers'
import { type Log, type Result, AbiCoder, concat, dataSlice, id } from 'ethers'

import CommitStore_1_2_ABI from '../abi/CommitStore_1_2.js'
import CommitStore_1_5_ABI from '../abi/CommitStore_1_5.js'
Expand Down Expand Up @@ -113,8 +113,8 @@ export interface CCIPExecution {
timestamp: number
}

const EVMExtraArgsV1Tag = dataSlice(id('CCIP EVMExtraArgsV1'), 0, 4)
const EVMExtraArgsV2Tag = dataSlice(id('CCIP EVMExtraArgsV2'), 0, 4)
const EVMExtraArgsV1Tag = id('CCIP EVMExtraArgsV1').substring(0, 10) as '0x97a657c9'
const EVMExtraArgsV2Tag = id('CCIP EVMExtraArgsV2').substring(0, 10) as '0x181dcf10'
const EVMExtraArgsV1 = 'tuple(uint256 gasLimit)'
const EVMExtraArgsV2 = 'tuple(uint256 gasLimit, bool allowOutOfOrderExecution)'
export interface EVMExtraArgsV1 {
Expand All @@ -139,3 +139,24 @@ export function encodeExtraArgs(args: EVMExtraArgsV1 | EVMExtraArgsV2): string {
}
return '0x'
}

/**
* Parses extra arguments from CCIP messages
* @param data - extra arguments bytearray data
* @returns extra arguments object if found
**/
export function parseExtraArgs(data: string):
| ((EVMExtraArgsV1 | EVMExtraArgsV2) & {
_tag: 'EVMExtraArgsV1' | 'EVMExtraArgsV2'
})
| undefined {
if (data === '0x') return { _tag: 'EVMExtraArgsV1' }
if (data.startsWith(EVMExtraArgsV1Tag)) {
const args = defaultAbiCoder.decode([EVMExtraArgsV1], dataSlice(data, 4))
return { ...(args[0] as Result).toObject(), _tag: 'EVMExtraArgsV1' }
}
if (data.startsWith(EVMExtraArgsV2Tag)) {
const args = defaultAbiCoder.decode([EVMExtraArgsV2], dataSlice(data, 4))
return { ...(args[0] as Result).toObject(), _tag: 'EVMExtraArgsV2' }
}
}
13 changes: 10 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,19 @@ function formatData(name: string, data: string, parseError = false): Record<stri
return formatArray(name, split)
}

export function formatResult(result: unknown): unknown {
export function formatResult(
result: unknown,
parseValue?: (val: unknown, key: string | number) => unknown,
): unknown {
if (!(result instanceof Result)) return result
try {
const res = result.toObject()
if (!(Object.keys(res)[0] ?? '').match(/^[a-z]/)) throw new Error('Not an object')
for (const [k, v] of Object.entries(res)) {
if (v instanceof Result) {
res[k] = formatResult(v)
res[k] = formatResult(v, parseValue)
} else if (parseValue) {
res[k] = parseValue(v, k)
}
}
return res
Expand All @@ -154,7 +159,9 @@ export function formatResult(result: unknown): unknown {
for (let i = 0; i < res.length; i++) {
const v = res[i] as unknown
if (v instanceof Result) {
res[i] = formatResult(v)
res[i] = formatResult(v, parseValue)
} else if (parseValue) {
res[i] = parseValue(v, i)
}
}
return res
Expand Down

0 comments on commit c55978f

Please sign in to comment.