From 29403f402ba10b1411dc9297b409d4b648964fc0 Mon Sep 17 00:00:00 2001 From: Siegfried Puchbauer Date: Mon, 8 Jun 2020 15:21:46 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20New=20config=20paramter?= =?UTF-8?q?=20to=20skip=20contract=20match=20requirement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added new config parameter `requireContractMatch`, which - when set to `false` - skips the requirement for maching a contract using the deployed contract address or a fingerprint in order for ethlogger to emit the names of parameters for decoded calls and events --- config.schema.json | 4 ++++ defaults.ethlogger.yaml | 1 + docs/configuration.md | 15 ++++++++------- src/abi/repo.ts | 10 ++++++---- src/config.ts | 8 ++++++++ test/abi/contract.test.ts | 1 + test/abi/files.test.ts | 2 ++ test/abi/repo.test.ts | 3 +++ test/abi/testcases/ethdenver.test.ts | 1 + test/abi/testcases/ost.test.ts | 1 + test/blockwatcher.test.ts | 1 + test/config.test.ts | 1 + 12 files changed, 37 insertions(+), 11 deletions(-) diff --git a/config.schema.json b/config.schema.json index 8850fa9..1d65769 100644 --- a/config.schema.json +++ b/config.schema.json @@ -20,6 +20,10 @@ "description": "If enabled, the ABI repsitory will creates hashes of all function and event signatures of an ABI\n(the hash is the fingerprint) and match it against the EVM bytecode obtained from live smart contracts\nwe encounter.", "type": "boolean" }, + "requireContractMatch": { + "description": "If enabled, signature matches will be treated as anonyomous (parameter names will be omitted from\nthe output) if a contract cannot be tied to an ABI definition via either a fingerprint match,\nor a contract address match (when the ABI file includes the address of the deployed contract).\nEnabled by default. Setting this to `false` will output parameter names for any matching signature.", + "type": "boolean" + }, "searchRecursive": { "description": "`true` to search ABI directory recursively for ABI files", "type": "boolean" diff --git a/defaults.ethlogger.yaml b/defaults.ethlogger.yaml index a1024e3..6366598 100644 --- a/defaults.ethlogger.yaml +++ b/defaults.ethlogger.yaml @@ -63,6 +63,7 @@ abi: abiFileExtension: .json searchRecursive: true fingerprintContracts: true + requireContractMatch: true decodeAnonymous: true contractInfo: maxCacheEntries: 25000 diff --git a/docs/configuration.md b/docs/configuration.md index d75b2f8..ce57077 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -212,13 +212,14 @@ The checkpoint is where ethlogger keeps track of its state, which blocks have al The ABI repository is used to decode ABI information from smart contract calls and event logs. It generates and adds some additional information in transactions and events, including smart contract method call parameter names, values and data types, as well as smart contract names associated with a particular contract address. -| Name | Type | Description | -| ---------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `directory` | `string` | If specified, the ABI repository will recursively search this directory for ABI files | -| `searchRecursive` | `boolean` | `true` to search ABI directory recursively for ABI files | -| `abiFileExtension` | `string` | Set to `.json` by default as the file extension for ABIs | -| `fingerprintContracts` | `boolean` | If enabled, the ABI repsitory will creates hashes of all function and event signatures of an ABI (the hash is the fingerprint) and match it against the EVM bytecode obtained from live smart contracts we encounter. | -| `decodeAnonymous` | `boolean` | If enabled, ethlogger will attempt to decode function calls and event logs using a set of common signatures as a fallback if no match against any supplied ABI definition was found. | +| Name | Type | Description | +| ---------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `directory` | `string` | If specified, the ABI repository will recursively search this directory for ABI files | +| `searchRecursive` | `boolean` | `true` to search ABI directory recursively for ABI files | +| `abiFileExtension` | `string` | Set to `.json` by default as the file extension for ABIs | +| `fingerprintContracts` | `boolean` | If enabled, the ABI repsitory will creates hashes of all function and event signatures of an ABI (the hash is the fingerprint) and match it against the EVM bytecode obtained from live smart contracts we encounter. | +| `requireContractMatch` | `boolean` | If enabled, signature matches will be treated as anonyomous (parameter names will be omitted from the output) if a contract cannot be tied to an ABI definition via either a fingerprint match, or a contract address match (when the ABI file includes the address of the deployed contract). Enabled by default. Setting this to `false` will output parameter names for any matching signature. | +| `decodeAnonymous` | `boolean` | If enabled, ethlogger will attempt to decode function calls and event logs using a set of common signatures as a fallback if no match against any supplied ABI definition was found. | ### ContractInfo diff --git a/src/abi/repo.ts b/src/abi/repo.ts index ba39020..12e04ac 100644 --- a/src/abi/repo.ts +++ b/src/abi/repo.ts @@ -79,18 +79,17 @@ export class AbiRepository implements ManagedResource { public async initialize() { const config = this.config; + debug('Initializing ABI repository with config %O', config); if (config.directory != null) { const abiCount = await this.loadAbisFromDir(config.directory!, config); info('Loaded %d ABIs from directory %s', abiCount, config.directory); } - if (config.decodeAnonymous) { const fnCount = await this.loadAnonymousSignatures(joinPath(__dirname, '../../data/fns.abisigs.gz')); const evCount = await this.loadAnonymousSignatures(joinPath(__dirname, '../../data/evts.abisigs.gz')); info('Loaded %d anonymous ABI signatures from built-in signature files', fnCount + evCount); } } - public async loadAnonymousSignatures(file: string): Promise { debug('Loading anonymous signatures from %s', file); let count = 0; @@ -206,6 +205,9 @@ export class AbiRepository implements ManagedResource { } } if (match.length > 0) { + if (this.config.requireContractMatch === false) { + return { candidates: match, anonymous: false }; + } return { candidates: match, anonymous: true }; } if (TRACE_ENABLED) { @@ -222,7 +224,7 @@ export class AbiRepository implements ManagedResource { private abiDecode( sigHash: string, matchParams: AbiMatchParams, - decodeFn: (abis: AbiItemDefinition[], anonymous: boolean) => T + decoder: (abis: AbiItemDefinition[], anonymous: boolean) => T ): T | undefined { const matchingAbis = this.findMatchingAbis(sigHash, matchParams); trace('Found %d matching ABIs for signature %s', matchingAbis?.candidates?.length ?? 0, sigHash); @@ -236,7 +238,7 @@ export class AbiRepository implements ManagedResource { return; } try { - return decodeFn(matchingAbis.candidates, matchingAbis!.anonymous); + return decoder(matchingAbis.candidates, matchingAbis.anonymous); } catch (e) { if (matchingAbis!.anonymous) { debug('Failed to decode anonymous ABI', e); diff --git a/src/config.ts b/src/config.ts index 7066e40..1644469 100644 --- a/src/config.ts +++ b/src/config.ts @@ -152,6 +152,13 @@ export interface AbiRepositoryConfigSchema { * we encounter. */ fingerprintContracts: boolean; + /** + * If enabled, signature matches will be treated as anonyomous (parameter names will be omitted from + * the output) if a contract cannot be tied to an ABI definition via either a fingerprint match, + * or a contract address match (when the ABI file includes the address of the deployed contract). + * Enabled by default. Setting this to `false` will output parameter names for any matching signature. + */ + requireContractMatch: boolean; /** * If enabled, ethlogger will attempt to decode function calls and event logs using a set of * common signatures as a fallback if no match against any supplied ABI definition was found. @@ -765,6 +772,7 @@ export async function loadEthloggerConfig(flags: CliFlags, dryRun: boolean = fal directory: flags['abi-dir'] ?? defaults.abi?.directory, abiFileExtension: defaults.abi?.abiFileExtension, fingerprintContracts: defaults.abi?.fingerprintContracts ?? true, + requireContractMatch: defaults.abi?.requireContractMatch ?? true, decodeAnonymous: defaults.abi?.decodeAnonymous ?? true, }, blockWatcher: { diff --git a/test/abi/contract.test.ts b/test/abi/contract.test.ts index 7fa8fcf..b8e165f 100644 --- a/test/abi/contract.test.ts +++ b/test/abi/contract.test.ts @@ -8,6 +8,7 @@ test('extractFunctionsAndEvents', async () => { decodeAnonymous: false, fingerprintContracts: true, abiFileExtension: '.json', + requireContractMatch: true, }; const abis = new AbiRepository(config); await abis.loadAbiFile(join(__dirname, '../abis/BCB.json'), config); diff --git a/test/abi/files.test.ts b/test/abi/files.test.ts index fffea17..97a0abc 100644 --- a/test/abi/files.test.ts +++ b/test/abi/files.test.ts @@ -7,6 +7,7 @@ describe('loadAbiFile', () => { decodeAnonymous: false, fingerprintContracts: true, directory: join(__dirname, '../abis'), + requireContractMatch: true, }); expect(result).toMatchSnapshot(); @@ -18,6 +19,7 @@ describe('loadAbiFile', () => { decodeAnonymous: false, fingerprintContracts: true, directory: join(__dirname, '..'), + requireContractMatch: true, }) ).resolves.toMatchSnapshot(); }); diff --git a/test/abi/repo.test.ts b/test/abi/repo.test.ts index 483bb40..6ac79a9 100644 --- a/test/abi/repo.test.ts +++ b/test/abi/repo.test.ts @@ -19,6 +19,7 @@ test('AbiRepository#decodeFunctionCall', async () => { abiFileExtension: '.json', directory: join(__dirname, '../abis'), searchRecursive: true, + requireContractMatch: true, }); await abiRepo.initialize(); @@ -125,6 +126,7 @@ test('AbiRepository#decodeLogEvent', async () => { abiFileExtension: '.json', directory: join(__dirname, '../abis'), searchRecursive: true, + requireContractMatch: true, }); await abiRepo.initialize(); @@ -182,6 +184,7 @@ test('decode anonymous with collision', async () => { const abiRepo = new AbiRepository({ decodeAnonymous: true, fingerprintContracts: false, + requireContractMatch: true, }); await abiRepo.initialize(); diff --git a/test/abi/testcases/ethdenver.test.ts b/test/abi/testcases/ethdenver.test.ts index 07b4c96..87c4d2d 100644 --- a/test/abi/testcases/ethdenver.test.ts +++ b/test/abi/testcases/ethdenver.test.ts @@ -38,6 +38,7 @@ test('blockwatcher', async () => { abiFileExtension: '.json', directory: join(__dirname, '../../abis'), searchRecursive: true, + requireContractMatch: true, }); await abiRepo.initialize(); const checkpoints = new Checkpoint({ diff --git a/test/abi/testcases/ost.test.ts b/test/abi/testcases/ost.test.ts index f4ea96a..cdcf053 100644 --- a/test/abi/testcases/ost.test.ts +++ b/test/abi/testcases/ost.test.ts @@ -38,6 +38,7 @@ test('blockwatcher', async () => { abiFileExtension: '.json', directory: join(__dirname, 'abis'), searchRecursive: true, + requireContractMatch: true, }); await abiRepo.initialize(); const checkpoints = new Checkpoint({ diff --git a/test/blockwatcher.test.ts b/test/blockwatcher.test.ts index 6e08426..3bad8be 100644 --- a/test/blockwatcher.test.ts +++ b/test/blockwatcher.test.ts @@ -34,6 +34,7 @@ test('blockwatcher', async () => { abiFileExtension: '.json', directory: join(__dirname, './abis'), searchRecursive: true, + requireContractMatch: true, }); await abiRepo.initialize(); const checkpoints = new Checkpoint({ diff --git a/test/config.test.ts b/test/config.test.ts index 4edd485..fc290ee 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -15,6 +15,7 @@ test('defaults', async () => { "decodeAnonymous": true, "directory": undefined, "fingerprintContracts": true, + "requireContractMatch": true, }, "blockWatcher": Object { "blocksMaxChunkSize": 25,