From dc754ce6e501664638417500636a5fad7185f829 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 24 May 2023 19:50:52 -0400 Subject: [PATCH 1/7] Allow decoder to use 4byte.directory to decode (minus the actual signature fetching) --- packages/codec/lib/types.ts | 26 +++- packages/decoder/lib/decoders.ts | 161 ++++++++++++++++++----- packages/decoder/lib/fetch-signatures.ts | 7 + 3 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 packages/decoder/lib/fetch-signatures.ts diff --git a/packages/codec/lib/types.ts b/packages/codec/lib/types.ts index 9bd5561ba72..c657f470c39 100644 --- a/packages/codec/lib/types.ts +++ b/packages/codec/lib/types.ts @@ -222,9 +222,18 @@ export interface MessageDecoding { * Further information about how the decoding may be interpreted. Note that interpretations * may be added by things that use @truffle/codec, such as @truffle/decoder, rather than by * @truffle/codec itself. See individual interpretations for details. - * (Currently there are none for this type.) */ - interpretations: {}; + interpretations: { + /** + * If we can't recognize the function, we attempt to see if we can decode based on the + * function selector alone using https://www.4byte.directory/ to determine possible + * signatures. Note these decodings will necessarily be made in ABI mode, and will + * even be lacking names of struct members. + * + * This interpretation will only be present if the array is nonempty. + */ + selectorBasedDecodings?: FunctionDecoding[]; + }; } /** @@ -251,9 +260,18 @@ export interface UnknownCallDecoding { * Further information about how the decoding may be interpreted. Note that interpretations * may be added by things that use @truffle/codec, such as @truffle/decoder, rather than by * @truffle/codec itself. See individual interpretations for details. - * (Currently there are none for this type.) */ - interpretations: {}; + interpretations: { + /** + * If we can't recognize the function, we attempt to see if we can decode based on the + * function selector alone using https://www.4byte.directory/ to determine possible + * signatures. Note these decodings will necessarily be made in ABI mode, and will + * even be lacking names of struct members. + * + * This interpretation will only be present if the array is nonempty. + */ + selectorBasedDecodings?: FunctionDecoding[]; + }; } /** diff --git a/packages/decoder/lib/decoders.ts b/packages/decoder/lib/decoders.ts index 0347674ecb9..d88c9c07780 100644 --- a/packages/decoder/lib/decoders.ts +++ b/packages/decoder/lib/decoders.ts @@ -43,6 +43,7 @@ import { ArrayIndexOutOfBoundsError, NoProviderError } from "./errors"; +import { fetchSignatures } from "./fetch-signatures"; import { Shims } from "@truffle/compile-common"; //sorry for untyped imports! const SourceMapUtils = require("@truffle/source-map-utils"); @@ -433,17 +434,20 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { const block = transaction.blockNumber; const blockNumber = await this.regularizeBlock(block); const isConstructor = transaction.to === null; - const context = await this.getContextByAddress( - transaction.to, - blockNumber, - transaction.input, - additionalContexts - ); + const context = + overrideContext || + (await this.getContextByAddress( + transaction.to, + blockNumber, + transaction.input, + additionalContexts + )); let allocations = this.allocations; if (context && !(context.context in this.contexts)) { @@ -464,6 +468,7 @@ export class ProjectDecoder { } const data = Conversion.toBytes(transaction.input); + const contexts = { ...this.deployedContexts, ...additionalContexts }; const info: Evm.EvmInfo = { state: { storage: {}, @@ -471,7 +476,7 @@ export class ProjectDecoder { }, userDefinedTypes: this.userDefinedTypes, allocations, - contexts: { ...this.deployedContexts, ...additionalContexts }, + contexts, currentContext: context }; const decoder = decodeCalldata(info, isConstructor); @@ -500,8 +505,23 @@ export class ProjectDecoder { decoding, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext + ); + } + + //...and 4byte.directory processing + if (decoding.kind === "message" || decoding.kind === "unknown") { + const selectorBasedDecodings = await this.decodeTransactionBySelector( + transaction, + data, //this is redundant but included for convenience + additionalContexts, + context ); + if (selectorBasedDecodings.length > 0) { + decoding.interpretations.selectorBasedDecodings = + selectorBasedDecodings; + } } return decoding; @@ -882,7 +902,8 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { //first, let's clone our decoding and its interpretations decoding = { @@ -898,33 +919,38 @@ export class ProjectDecoder { decoding, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ); decoding.interpretations.aggregate = await this.interpretAggregate( decoding, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ); decoding.interpretations.tryAggregate = await this.interpretTryAggregate( decoding, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ); decoding.interpretations.deadlinedMulticall = await this.interpretDeadlinedMulticall( decoding, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ); decoding.interpretations.specifiedBlockhashMulticall = await this.interpretBlockhashedMulticall( decoding, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ); return decoding; @@ -938,7 +964,8 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise<(Codec.CalldataDecoding | null)[] | undefined> { if ( decoding.kind === "function" && @@ -957,7 +984,8 @@ export class ProjectDecoder { callResult as Format.Values.BytesResult, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ) ) ); @@ -974,14 +1002,16 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { switch (callResult.kind) { case "value": return await this.decodeTransactionWithAdditionalContexts( { ...transaction, input: callResult.value.asHex }, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ); case "error": return null; @@ -996,7 +1026,8 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { if ( decoding.kind === "function" && @@ -1021,7 +1052,8 @@ export class ProjectDecoder { | Format.Values.TupleResult, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ) ) ); @@ -1038,7 +1070,8 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { switch (callResult.kind) { case "value": @@ -1065,7 +1098,8 @@ export class ProjectDecoder { to: address }, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ); return { address, decoding: subDecoding }; case "error": @@ -1084,7 +1118,8 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { if ( decoding.kind === "function" && @@ -1114,7 +1149,8 @@ export class ProjectDecoder { | Format.Values.TupleResult, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ) ) ); @@ -1132,7 +1168,8 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { if ( decoding.kind === "function" && @@ -1156,7 +1193,8 @@ export class ProjectDecoder { callResult as Format.Values.BytesResult, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ) ) ); @@ -1174,7 +1212,8 @@ export class ProjectDecoder { [ selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; - } + }, + overrideContext?: Contexts.Context ): Promise { if ( decoding.kind === "function" && @@ -1198,7 +1237,8 @@ export class ProjectDecoder { callResult as Format.Values.BytesResult, transaction, additionalContexts, - additionalAllocations + additionalAllocations, + overrideContext ) ) ); @@ -1207,6 +1247,67 @@ export class ProjectDecoder { return undefined; } } + + private async decodeTransactionBySelector( + transaction: DecoderTypes.Transaction, + data: Uint8Array, //this is redundant but included for convenience + additionalContexts: Contexts.Contexts, + context: Contexts.Context | null + ): Promise { + if (data.length < Evm.Utils.SELECTOR_SIZE) { + return []; + } + const selector = data.slice(0, Evm.Utils.SELECTOR_SIZE); + const signatures: string[] = await fetchSignatures(selector); + const abis = signatures.map(Abi.parseFunctionSignature); + const fakeContextHash = + "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; //hash of empty string + //the particular choice here doesn't really matter, but I figured let's pick something that will never + //occur as a real context hash + //(remember that context hashes are currently taken of the ASCII hex string, so the input + //will never be empty) + let decodings: FunctionDecoding[] = []; + for (const abiEntry of abis) { + //create a fake context with only this one ABI entry + //we can't do it with multiple of them because codec is not prepared + //for the idea that multiple might match + const fakeContextAbi = { [Abi.abiSelector(abiEntry)]: abiEntry }; + const fakeContext = context + ? { ...context, abi: fakeContextAbi } + : { + abi: fakeContextAbi, + context: fakeContextHash, + binary: "0x", + isConstructor: false + }; + const additionalAllocations = Object.values( + AbiData.Allocate.getCalldataAllocations( + [ + { + abi: [abiEntry], + compiler: null, + contractNode: null, + deployedContext: fakeContext + //we won't need anything else + } + ], + {}, //we won't need the reference declarations + {}, //we won't need the user-defined types + {} //we won't need the struct allocations + ).functionAllocations + )[0]; + const decoding = await this.decodeTransactionWithAdditionalContexts( + transaction, + additionalContexts, + additionalAllocations, + fakeContext //force decoding to use the fake context & not attempt to detect + ); + if (decoding.kind === "function") { + decodings.push(decoding); + } + } + return decodings; + } } /** @@ -1400,7 +1501,7 @@ export class ContractDecoder { const blockNumber = await this.regularizeBlock(block); const status = options.status; //true, false, or undefined - const selector = AbiData.Utils.abiSelector(abi); + const selector = Abi.abiSelector(abi); let allocation: AbiData.Allocate.ReturndataAllocation; if (this.contextHash !== undefined) { allocation = diff --git a/packages/decoder/lib/fetch-signatures.ts b/packages/decoder/lib/fetch-signatures.ts new file mode 100644 index 00000000000..c98dc8bcf30 --- /dev/null +++ b/packages/decoder/lib/fetch-signatures.ts @@ -0,0 +1,7 @@ +import debugModule from "debug"; +const debug = debugModule("decoder:fetch-signatures"); + +export async function fetchSignatures(selector: Uint8Array): Promise { + debug("selector: %O", selector); + return []; //TODO +} From 0638572e94a025cd49e94f843e5856d1f0ebdf05 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 24 May 2023 20:25:32 -0400 Subject: [PATCH 2/7] Add code to actually get signatures from 4byte.directory --- packages/decoder/lib/decoders.ts | 1 + packages/decoder/lib/fetch-signatures.ts | 57 +++++++++++++++++++++++- packages/decoder/package.json | 2 + 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/decoder/lib/decoders.ts b/packages/decoder/lib/decoders.ts index d88c9c07780..9c2389a5dfc 100644 --- a/packages/decoder/lib/decoders.ts +++ b/packages/decoder/lib/decoders.ts @@ -1259,6 +1259,7 @@ export class ProjectDecoder { } const selector = data.slice(0, Evm.Utils.SELECTOR_SIZE); const signatures: string[] = await fetchSignatures(selector); + debug("signatures: %O", signatures); const abis = signatures.map(Abi.parseFunctionSignature); const fakeContextHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; //hash of empty string diff --git a/packages/decoder/lib/fetch-signatures.ts b/packages/decoder/lib/fetch-signatures.ts index c98dc8bcf30..bcbdceb1549 100644 --- a/packages/decoder/lib/fetch-signatures.ts +++ b/packages/decoder/lib/fetch-signatures.ts @@ -1,7 +1,60 @@ import debugModule from "debug"; const debug = debugModule("decoder:fetch-signatures"); +import axios from "axios"; +import retry from "async-retry"; + +import { Conversion } from "@truffle/codec"; + +interface DirectoryResponse { + next: string | null; + previous: string | null; + count: number; + results: DirectoryEntry[]; +} + +interface DirectoryEntry { + id: number; + created_at: string; + text_signature: string; + hex_signature: string; //the only part we care about! + bytes_signature: string; +} + +//note: input should be 4 bytes long export async function fetchSignatures(selector: Uint8Array): Promise { - debug("selector: %O", selector); - return []; //TODO + const selectorString = Conversion.toHexString(selector); + let page: number = 1; + let signatures: string[] = []; + while (true) { + const response = await getSuccessfulResponse(selectorString, page); + signatures = signatures.concat( + response.results.map(({ text_signature }) => text_signature) + ); //append new signatures + if (response.next === null) { + break; + } + page++; //ideally we'd use the actual value of response.next, but this is easier + } + return signatures; +} + +async function getSuccessfulResponse( + selector: string, + page: number +): Promise { + return await retry( + async () => + ( + await axios.get("https://www.4byte.directory/api/v1/signatures/", { + params: { + hex_signature: selector, + page + }, + responseType: "json", + maxRedirects: 50 + }) + ).data, + { retries: 3 } //we'll leave minTimeout as the default 1000 + ); } diff --git a/packages/decoder/package.json b/packages/decoder/package.json index 737319ac2a8..c3a056a66dd 100644 --- a/packages/decoder/package.json +++ b/packages/decoder/package.json @@ -31,6 +31,8 @@ "@truffle/compile-common": "^0.9.5", "@truffle/encoder": "^1.0.16", "@truffle/source-map-utils": "^1.3.112", + "async-retry": "^1.3.1", + "axios": "1.2.4", "bn.js": "^5.1.3", "debug": "^4.3.1", "web3-utils": "1.10.0" From 3208348e8fd953769669c4caa3604d9a30a206c2 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 24 May 2023 21:30:46 -0400 Subject: [PATCH 3/7] Add tests of selector-based decodings --- packages/decoder/lib/decoders.ts | 2 + packages/decoder/package.json | 1 + .../decoder/test/current/contracts/Sink.sol | 10 + .../current/migrations/2_deploy_contracts.js | 2 + .../test/current/test/selector-based-test.js | 434 ++++++++++++++++++ 5 files changed, 449 insertions(+) create mode 100644 packages/decoder/test/current/contracts/Sink.sol create mode 100644 packages/decoder/test/current/test/selector-based-test.js diff --git a/packages/decoder/lib/decoders.ts b/packages/decoder/lib/decoders.ts index 9c2389a5dfc..b98820b7a63 100644 --- a/packages/decoder/lib/decoders.ts +++ b/packages/decoder/lib/decoders.ts @@ -1304,8 +1304,10 @@ export class ProjectDecoder { fakeContext //force decoding to use the fake context & not attempt to detect ); if (decoding.kind === "function") { + debug("accepted"); decodings.push(decoding); } + debug("moving on"); } return decodings; } diff --git a/packages/decoder/package.json b/packages/decoder/package.json index c3a056a66dd..48d32a9f3ee 100644 --- a/packages/decoder/package.json +++ b/packages/decoder/package.json @@ -51,6 +51,7 @@ "ganache": "7.8.0", "lodash": "^4.17.21", "mocha": "10.1.0", + "sinon": "^9.0.2", "tmp": "^0.2.1", "typescript": "^4.7.4", "web3": "1.10.0" diff --git a/packages/decoder/test/current/contracts/Sink.sol b/packages/decoder/test/current/contracts/Sink.sol new file mode 100644 index 00000000000..b96f1a2d714 --- /dev/null +++ b/packages/decoder/test/current/contracts/Sink.sol @@ -0,0 +1,10 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.8; +pragma experimental ABIEncoderV2; + +contract Sink { + constructor() payable { + } + fallback() external payable { + } +} diff --git a/packages/decoder/test/current/migrations/2_deploy_contracts.js b/packages/decoder/test/current/migrations/2_deploy_contracts.js index df3dec116bd..c1037474f52 100644 --- a/packages/decoder/test/current/migrations/2_deploy_contracts.js +++ b/packages/decoder/test/current/migrations/2_deploy_contracts.js @@ -6,6 +6,7 @@ const WireTestRedHerring = artifacts.require("WireTestRedHerring"); const ReceiveTest = artifacts.require("ReceiveTest"); const FallbackTest = artifacts.require("FallbackTest"); const DecodingSample = artifacts.require("DecodingSample"); +const Sink = artifacts.require("Sink"); module.exports = function (deployer) { deployer.deploy(DecoyLibrary); @@ -17,4 +18,5 @@ module.exports = function (deployer) { deployer.deploy(FallbackTest); deployer.deploy(DecodingSample); deployer.deploy(WireTestRedHerring); + deployer.deploy(Sink); }; diff --git a/packages/decoder/test/current/test/selector-based-test.js b/packages/decoder/test/current/test/selector-based-test.js new file mode 100644 index 00000000000..03c0147a1e2 --- /dev/null +++ b/packages/decoder/test/current/test/selector-based-test.js @@ -0,0 +1,434 @@ +const debug = require("debug")("decoder:test:selector-based-test"); +const assert = require("chai").assert; +const sinon = require("sinon"); +const Ganache = require("ganache"); +const path = require("path"); +const Web3 = require("web3"); +const axios = require("axios"); + +const Decoder = require("../../.."); +const Codec = require("@truffle/codec"); + +const { prepareContracts } = require("../../helpers"); + +beforeEach(function () { + sinon + .stub(axios, "get") + .withArgs( + "https://www.4byte.directory/api/v1/signatures/", + sinon.match({ + params: sinon.match({ + hex_signature: "0x00000000", + page: 1 + }) + }) + ) + .returns({ status: 200, data: zeroSelectorResults }); + axios.get.callThrough(); +}); + +afterEach(function () { + //restoring stubs + axios.get.restore(); +}); + +describe("Selector-based decoding", function () { + let provider; + let abstractions; + let web3; + + before("Create Provider", async function () { + provider = Ganache.provider({ + miner: { instamine: "strict" }, + seed: "decoder", + gasLimit: 7000000, + logging: { quiet: true } + }); + web3 = new Web3(provider); + }); + + after(async () => { + provider && (await provider.disconnect()); + }); + + before("Prepare contracts and artifacts", async function () { + this.timeout(30000); + + const prepared = await prepareContracts( + provider, + path.resolve(__dirname, "..") + ); + abstractions = prepared.abstractions; + }); + + it("uses selector-based-decoding on known contracts", async function () { + this.timeout(4000); + const deployedContract = await abstractions.Sink.deployed(); + + const decoder = await Decoder.forProject({ + provider: web3.currentProvider, + projectInfo: { artifacts: [abstractions.Sink] } + }); + + const truffleReceipt = await deployedContract.sendTransaction({ + //selector: 0 + //first argument: 2^255+1 + //second argument: 1 + data: "0x0000000010000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000001" + }); + const tx = await web3.eth.getTransaction(truffleReceipt.tx); + + const overallDecoding = await decoder.decodeTransaction(tx); + + assert.equal(overallDecoding.kind, "message"); + const decodings = overallDecoding.interpretations.selectorBasedDecodings; + assert.isDefined(decodings, "interpretation was not attached"); + assert.lengthOf(decodings, 2); + let decoding = decodings[0]; + assert.equal(decoding.kind, "function"); + assert.equal( + Codec.AbiData.Utils.abiSignature(decoding.abi), + "randallAteMySandwich_dbohban(uint256,address)" + ); + assert.equal(decoding.decodingMode, "abi"); + decoding = decodings[1]; + assert.equal(decoding.kind, "function"); + assert.equal( + Codec.AbiData.Utils.abiSignature(decoding.abi), + "randallAteMySandwich_bixiwot(uint256,uint256)" + ); + assert.equal(decoding.decodingMode, "abi"); + }); + + it("uses selector-based-decoding on unknown contracts", async function () { + this.timeout(4000); + const deployedContract = await abstractions.Sink.deployed(); + + const decoder = await Decoder.forProject({ + provider: web3.currentProvider, + projectInfo: { artifacts: [] } //no info given! + }); + + const truffleReceipt = await deployedContract.sendTransaction({ + //selector: 0 + //first argument: 2^255+1 + //second argument: 1 + data: "0x0000000010000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000001" + }); + const tx = await web3.eth.getTransaction(truffleReceipt.tx); + + const overallDecoding = await decoder.decodeTransaction(tx); + + assert.equal(overallDecoding.kind, "unknown"); + const decodings = overallDecoding.interpretations.selectorBasedDecodings; + assert.isDefined(decodings, "interpretation was not attached"); + assert.lengthOf(decodings, 2); + let decoding = decodings[0]; + assert.equal(decoding.kind, "function"); + assert.equal( + Codec.AbiData.Utils.abiSignature(decoding.abi), + "randallAteMySandwich_dbohban(uint256,address)" + ); + assert.equal(decoding.decodingMode, "abi"); + decoding = decodings[1]; + assert.equal(decoding.kind, "function"); + assert.equal( + Codec.AbiData.Utils.abiSignature(decoding.abi), + "randallAteMySandwich_bixiwot(uint256,uint256)" + ); + assert.equal(decoding.decodingMode, "abi"); + }); + + it("excludes selector-based interpretation when no matches", async function () { + this.timeout(4000); + const deployedContract = await abstractions.Sink.deployed(); + + const decoder = await Decoder.forProject({ + provider: web3.currentProvider, + projectInfo: { artifacts: [abstractions.Sink] } + }); + + const truffleReceipt = await deployedContract.sendTransaction({ + //selector: 0 + //first word: 256^20 + //second word: 33 + //third word: 0 + data: "0x00000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000000" + }); + const tx = await web3.eth.getTransaction(truffleReceipt.tx); + + const overallDecoding = await decoder.decodeTransaction(tx); + + assert.equal(overallDecoding.kind, "message"); + assert.notProperty( + overallDecoding.interpretations, + "selectorBasedDecodings" + ); + }); +}); + +const zeroSelectorResults = { + count: 36, + next: null, + previous: null, + results: [ + { + id: 1056298, + created_at: "2023-05-10T19:08:23.426890Z", + text_signature: "wycpnbqcyf()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 1036589, + created_at: "2023-03-25T07:03:28.040892Z", + text_signature: + "randallAteMySandwich(uint256[],address[],uint8,uint256[],bool,bytes32,address,address[])", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 951166, + created_at: "2023-03-05T23:22:45.375981Z", + text_signature: "QKJ0gRzrmgZzBLWm(address,uint256,uint256,bytes)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 911332, + created_at: "2023-02-01T15:09:28.195737Z", + text_signature: "MonkahmmXXXXXXXXXXXXACSKFIOY()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 849939, + created_at: "2023-01-29T17:26:07.960435Z", + text_signature: + "fulfillBasicOrder_efficient_fGEoT((((uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint256,uint256,uint256,address,uint8),(bytes32,bytes32,uint8)))", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 849694, + created_at: "2023-01-26T22:58:56.766599Z", + text_signature: + "fulfillBasicOrder_efficient_6GL6yc((address,uint256,uint256,address,address,address,uint256,uint256,uint8,uint256,uint256,bytes32,uint256,bytes32,bytes32,uint256,(uint256,address)[],bytes))", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 847043, + created_at: "2022-11-13T20:42:52.822214Z", + text_signature: "randallsRevenge_ilxaotc()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 845490, + created_at: "2022-09-29T13:12:24.964508Z", + text_signature: + "contrivedNameThatisVeryUnlikelyToBeFoundInTheWild_visd4o(address,uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 843197, + created_at: "2022-07-15T00:33:17.701162Z", + text_signature: "arb_ybtltp(uint256[])", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 842144, + created_at: "2022-06-21T09:18:58.279122Z", + text_signature: "execute_44g58pv()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 841673, + created_at: "2022-06-16T23:15:56.944101Z", + text_signature: "f00000000_bvvvdlt()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 834833, + created_at: "2022-05-04T04:57:38.149119Z", + text_signature: "mint_efficient_1268F998()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 708136, + created_at: "2022-04-26T05:15:36.528304Z", + text_signature: "mint_d22vi9okr4w(address)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 574181, + created_at: "2022-04-21T03:39:41.906704Z", + text_signature: "f7836435186477227(address)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 569126, + created_at: "2022-04-21T02:44:40.330091Z", + text_signature: "jIUTh(bytes)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 569106, + created_at: "2022-04-21T02:21:20.584057Z", + text_signature: + "AaANwg8((address,address,address,uint136,uint40,uint40,uint24,uint8,uint256,bytes32,bytes32,uint256))", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 445887, + created_at: "2021-12-09T06:20:59.395454Z", + text_signature: "f09140466846285922(address,bytes)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 236056, + created_at: "2021-09-22T13:35:59.354885Z", + text_signature: "ROOT4146650865()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 233686, + created_at: "2021-07-31T18:21:35.717396Z", + text_signature: "bdspwouamqsxyabc(uint256,bytes)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 233606, + created_at: "2021-07-30T04:53:07.171127Z", + text_signature: "randallAteMySandwich_atrxxnf(bytes)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 197201, + created_at: "2021-07-24T03:53:17.360183Z", + text_signature: "mev_abcd1g3ekj2f4ih5(bytes)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 197200, + created_at: "2021-07-24T03:48:23.350500Z", + text_signature: "abcei51243fdgjkh(bytes)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 197199, + created_at: "2021-07-24T03:20:08.866418Z", + text_signature: "cehbdjakgfi(address,address,uint256,uint8)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 197126, + created_at: "2021-07-22T01:17:42.281634Z", + text_signature: "randallAteMySandwich_dbohban(uint256,address)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 197125, + created_at: "2021-07-22T01:08:18.060837Z", + text_signature: "randallAteMySandwich_bixiwot(uint256,uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 197124, + created_at: "2021-07-22T00:33:15.995821Z", + text_signature: "randall_was_here_eionbta(uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 188087, + created_at: "2021-05-28T13:40:31.281154Z", + text_signature: "buy_bca86a0f(address,uint256,int256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 175574, + created_at: "2021-02-23T10:26:30.674037Z", + text_signature: "test2001551086()", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 174836, + created_at: "2021-01-17T06:21:27.715566Z", + text_signature: "buyAndFree22457070633(uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 31771, + created_at: "2018-05-10T11:32:26.666918Z", + text_signature: "blockHashAskewLimitary(uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 31770, + created_at: "2018-05-10T11:32:16.370946Z", + text_signature: "blockHashAmphithyronVersify(uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 31769, + created_at: "2018-05-10T11:32:05.935426Z", + text_signature: "blockHashAmarilloNonspontaneously(uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 31768, + created_at: "2018-05-10T11:31:54.447181Z", + text_signature: "blockHashAddendsInexpansible(uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 31623, + created_at: "2018-04-28T18:00:27.736494Z", + text_signature: "left_branch_block(uint32)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 31614, + created_at: "2018-04-28T06:59:29.086101Z", + text_signature: + "overdiffusingness(bytes,uint256,uint256,uint256,uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + }, + { + id: 31613, + created_at: "2018-04-28T03:59:19.032719Z", + text_signature: "get_block_hash_257335279069929(uint256)", + hex_signature: "0x00000000", + bytes_signature: "\x00\x00\x00\x00" + } + ] +}; From 9065237e441b86ce5e3c5ff2ad04eb8901b25048 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 24 May 2023 21:47:50 -0400 Subject: [PATCH 4/7] Prevent infinite loops, apply strict mode, and ensure allocations are present --- packages/codec/lib/core.ts | 32 ++++++++++++++-- packages/codec/lib/format/errors.ts | 16 +++++++- packages/decoder/lib/decoders.ts | 58 +++++++++++++++++++++++------ 3 files changed, 90 insertions(+), 16 deletions(-) diff --git a/packages/codec/lib/core.ts b/packages/codec/lib/core.ts index f1da2d4d916..fad2a517cf8 100644 --- a/packages/codec/lib/core.ts +++ b/packages/codec/lib/core.ts @@ -51,7 +51,8 @@ export function* decodeVariable( */ export function* decodeCalldata( info: Evm.EvmInfo, - isConstructor?: boolean //ignored if context! trust context instead if have + isConstructor?: boolean, //ignored if context! trust context instead if have + strictAbiMode?: boolean //used for selector-based decoding ): Generator { const context = info.currentContext; if (context === null) { @@ -130,7 +131,8 @@ export function* decodeCalldata( try { value = yield* decode(dataType, argumentAllocation.pointer, info, { abiPointerBase: allocation.offset, //note the use of the offset for decoding pointers! - allowRetry: decodingMode === "full" + allowRetry: decodingMode === "full", + strictAbiMode }); } catch (error) { if ( @@ -156,7 +158,8 @@ export function* decodeCalldata( argumentAllocation.pointer, info, { - abiPointerBase: allocation.offset + abiPointerBase: allocation.offset, + strictAbiMode } ); //4. the remaining parameters will then automatically be decoded in ABI mode due to (1), @@ -174,6 +177,29 @@ export function* decodeCalldata( : { value } ); } + //if we're in strict mode, do a re-encoding check + if (strictAbiMode) { + const decodedArgumentValues = decodedArguments.map( + argument => argument.value + ); + const reEncodedData = AbiData.Encode.encodeTupleAbi( + decodedArgumentValues, + info.allocations.abi + ); + const encodedData = info.state.calldata.subarray(Evm.Utils.SELECTOR_SIZE); //slice off the selector + //NOTE: I'm assuming this isn't a constructor. right now strict mode is only used + //for functions, not constructors. if this is a constructor than the above line + //won't work properly... you'd want to strip off the whole bytecode. + if (!Evm.Utils.equalData(reEncodedData, encodedData)) { + //if not, this allocation doesn't work + debug("rejected due to mismatch"); + throw new StopDecodingError({ + kind: "ReEncodingMismatchError" as const, + data: encodedData, + reEncodedData + }); + } + } if (isConstructor) { return { kind: "constructor" as const, diff --git a/packages/codec/lib/format/errors.ts b/packages/codec/lib/format/errors.ts index 78664388e3f..0587443d2ee 100644 --- a/packages/codec/lib/format/errors.ts +++ b/packages/codec/lib/format/errors.ts @@ -979,7 +979,8 @@ export interface OverlargePointersNotImplementedError { */ export type InternalUseError = | OverlongArrayOrStringStrictModeError - | InternalFunctionInABIError; + | InternalFunctionInABIError + | ReEncodingMismatchError; /** * Error for the stricter length check in strict mode @@ -1000,3 +1001,16 @@ export interface OverlongArrayOrStringStrictModeError { export interface InternalFunctionInABIError { kind: "InternalFunctionInABIError"; } + +/** + * This doesn't really belong here, but we need something for + * when re-encoding doesn't match and we're decoding a transaction + * (since there we have to error instead of just skipping). + * + * @Category Internal-use errors + */ +export interface ReEncodingMismatchError { + kind: "ReEncodingMismatchError"; + data: Uint8Array; + reEncodedData: Uint8Array; +} diff --git a/packages/decoder/lib/decoders.ts b/packages/decoder/lib/decoders.ts index b98820b7a63..0657763800b 100644 --- a/packages/decoder/lib/decoders.ts +++ b/packages/decoder/lib/decoders.ts @@ -435,7 +435,8 @@ export class ProjectDecoder { selector: string ]: AbiData.Allocate.FunctionCalldataAndReturndataAllocation; }, - overrideContext?: Contexts.Context + overrideContext?: Contexts.Context, + isForSelectorBasedDecoding?: boolean ): Promise { const block = transaction.blockNumber; const blockNumber = await this.regularizeBlock(block); @@ -450,11 +451,24 @@ export class ProjectDecoder { )); let allocations = this.allocations; - if (context && !(context.context in this.contexts)) { - //if the context comes from additionalContexts, + if (overrideContext) { + //if we've got an override context, let's override some things + //(this branch is used when doing selector-based decoding) + allocations = { + ...this.allocations, + calldata: { + ...this.allocations.calldata, + functionAllocations: { + ...this.allocations.calldata.functionAllocations, + [context.context]: additionalAllocations + } + } + }; + } else if (context && !(context.context in this.contexts)) { + //otherwise, if the context comes from additionalContexts, //we'll add the additional allocations to the allocations; //however, we'll allow other allocations to override it... - //it's only supposed to be used if necessary! + //when we're not overriding, it's only supposed to be used if necessary! allocations = { ...this.allocations, calldata: { @@ -479,7 +493,11 @@ export class ProjectDecoder { contexts, currentContext: context }; - const decoder = decodeCalldata(info, isConstructor); + const decoder = decodeCalldata( + info, + isConstructor, + isForSelectorBasedDecoding + ); //turn on strict mode for selector-based decoding let result = decoder.next(); while (result.done === false) { @@ -511,7 +529,10 @@ export class ProjectDecoder { } //...and 4byte.directory processing - if (decoding.kind === "message" || decoding.kind === "unknown") { + if ( + (decoding.kind === "message" || decoding.kind === "unknown") && + !isForSelectorBasedDecoding //prevent infinite loops! + ) { const selectorBasedDecodings = await this.decodeTransactionBySelector( transaction, data, //this is redundant but included for convenience @@ -1297,12 +1318,25 @@ export class ProjectDecoder { {} //we won't need the struct allocations ).functionAllocations )[0]; - const decoding = await this.decodeTransactionWithAdditionalContexts( - transaction, - additionalContexts, - additionalAllocations, - fakeContext //force decoding to use the fake context & not attempt to detect - ); + let decoding: CalldataDecoding; + try { + decoding = await this.decodeTransactionWithAdditionalContexts( + transaction, + additionalContexts, + additionalAllocations, + fakeContext, //force decoding to use the fake context & not attempt to detect + true //prevent infinite loops! and turn on strict mode + ); + } catch (error) { + //because we're decoding in strict mode, it may error. + if (error instanceof Codec.StopDecodingError) { + //decoding didn't match, go to the next one + continue; + } else { + //unexpected error, rethrow! + throw error; + } + } if (decoding.kind === "function") { debug("accepted"); decodings.push(decoding); From 81afc7174b721c1fd6caf16c2839fbf6fa6ea78f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 24 May 2023 23:41:46 -0400 Subject: [PATCH 5/7] Account for constructors in decodeCalldata re-encoding check --- packages/codec/lib/core.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/codec/lib/core.ts b/packages/codec/lib/core.ts index fad2a517cf8..8109137da98 100644 --- a/packages/codec/lib/core.ts +++ b/packages/codec/lib/core.ts @@ -186,10 +186,11 @@ export function* decodeCalldata( decodedArgumentValues, info.allocations.abi ); - const encodedData = info.state.calldata.subarray(Evm.Utils.SELECTOR_SIZE); //slice off the selector - //NOTE: I'm assuming this isn't a constructor. right now strict mode is only used - //for functions, not constructors. if this is a constructor than the above line - //won't work properly... you'd want to strip off the whole bytecode. + const selectorLength = isConstructor + ? (context.binary.length - 2) / 2 //for a constructor, the bytecode acts as the "selector" + : //note we have to account for the fact that it's a hex string + Evm.Utils.SELECTOR_SIZE; + const encodedData = info.state.calldata.subarray(selectorLength); //slice off the selector if (!Evm.Utils.equalData(reEncodedData, encodedData)) { //if not, this allocation doesn't work debug("rejected due to mismatch"); From 237527124545056e5a49d805fca303b8b5533ed0 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 5 Jun 2023 15:57:51 -0400 Subject: [PATCH 6/7] Update comments --- packages/codec/lib/core.ts | 6 +++++- packages/codec/lib/format/errors.ts | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/codec/lib/core.ts b/packages/codec/lib/core.ts index 8109137da98..886e72bdec4 100644 --- a/packages/codec/lib/core.ts +++ b/packages/codec/lib/core.ts @@ -52,7 +52,11 @@ export function* decodeVariable( export function* decodeCalldata( info: Evm.EvmInfo, isConstructor?: boolean, //ignored if context! trust context instead if have - strictAbiMode?: boolean //used for selector-based decoding + strictAbiMode?: boolean //used for selector-based decoding. has two effects: + //1. sets the strictAbiMode option when calling decode(), causing it to throw + //if it encounters a problem rather than returning an ErrorResult; + //2. performs a re-encoding check at the end (like decodeEvevent and decodeReturndata) + //and throws if it fails ): Generator { const context = info.currentContext; if (context === null) { diff --git a/packages/codec/lib/format/errors.ts b/packages/codec/lib/format/errors.ts index 0587443d2ee..93715fcd180 100644 --- a/packages/codec/lib/format/errors.ts +++ b/packages/codec/lib/format/errors.ts @@ -1003,9 +1003,9 @@ export interface InternalFunctionInABIError { } /** - * This doesn't really belong here, but we need something for - * when re-encoding doesn't match and we're decoding a transaction - * (since there we have to error instead of just skipping). + * This isn't really an error that one can get as part of a `Result`, but we + * need something for when re-encoding doesn't match and we're decoding a + * transaction (since there we have to error instead of just skipping). * * @Category Internal-use errors */ From 29cd1619f96374f1586841a743b26252e51b794b Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 9 Jun 2023 00:05:09 -0400 Subject: [PATCH 7/7] Add options for selector-based decoding and disable by default --- packages/decoder/lib/decoders.ts | 20 +++++++++-- packages/decoder/lib/fetch-signatures.ts | 11 +++--- packages/decoder/lib/index.ts | 3 +- packages/decoder/lib/types.ts | 24 +++++++++++++ .../test/current/test/selector-based-test.js | 35 +++++++++++++++++-- 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/packages/decoder/lib/decoders.ts b/packages/decoder/lib/decoders.ts index 0657763800b..671c61b7833 100644 --- a/packages/decoder/lib/decoders.ts +++ b/packages/decoder/lib/decoders.ts @@ -49,6 +49,8 @@ import { Shims } from "@truffle/compile-common"; const SourceMapUtils = require("@truffle/source-map-utils"); const { default: ENS, getEnsAddress } = require("@ensdomains/ensjs"); +const defaultSelectorDirectory = "https://www.4byte.directory/api"; + /** * The ProjectDecoder class. Decodes transactions and logs. See below for a method listing. * @category Decoder @@ -72,6 +74,8 @@ export class ProjectDecoder { private ens: any | null; //any should be ENS, sorry >_> private ensSettings: DecoderTypes.EnsSettings; + private selectorDirectory: string | null; + private addProjectInfoNonce: number = 0; /** @@ -80,7 +84,8 @@ export class ProjectDecoder { constructor( compilations: Compilations.Compilation[], provider: Provider, - ensSettings?: DecoderTypes.EnsSettings + ensSettings?: DecoderTypes.EnsSettings, + selectorDirectory?: DecoderTypes.SelectorDirectorySettings ) { if (!provider) { throw new NoProviderError(); @@ -98,6 +103,10 @@ export class ProjectDecoder { ensSettings?.provider !== undefined ? ensSettings?.provider : provider, //importantly, null does not trigger this case registryAddress: ensSettings?.registryAddress }; //we don't use an object spread because we want undefined to be ignored + this.selectorDirectory = selectorDirectory?.enabled + ? selectorDirectory.url ?? defaultSelectorDirectory + : null; + let allocationInfo: AbiData.Allocate.ContractAllocationInfo[]; ({ @@ -1275,11 +1284,18 @@ export class ProjectDecoder { additionalContexts: Contexts.Contexts, context: Contexts.Context | null ): Promise { + //first: bail out if no directory + if (this.selectorDirectory === null) { + return []; + } if (data.length < Evm.Utils.SELECTOR_SIZE) { return []; } const selector = data.slice(0, Evm.Utils.SELECTOR_SIZE); - const signatures: string[] = await fetchSignatures(selector); + const signatures: string[] = await fetchSignatures( + selector, + this.selectorDirectory + ); debug("signatures: %O", signatures); const abis = signatures.map(Abi.parseFunctionSignature); const fakeContextHash = diff --git a/packages/decoder/lib/fetch-signatures.ts b/packages/decoder/lib/fetch-signatures.ts index bcbdceb1549..54fac5004d2 100644 --- a/packages/decoder/lib/fetch-signatures.ts +++ b/packages/decoder/lib/fetch-signatures.ts @@ -21,13 +21,15 @@ interface DirectoryEntry { bytes_signature: string; } -//note: input should be 4 bytes long -export async function fetchSignatures(selector: Uint8Array): Promise { +export async function fetchSignatures( + selector: Uint8Array, //note: input should be 4 bytes long + url: string +): Promise { const selectorString = Conversion.toHexString(selector); let page: number = 1; let signatures: string[] = []; while (true) { - const response = await getSuccessfulResponse(selectorString, page); + const response = await getSuccessfulResponse(selectorString, url, page); signatures = signatures.concat( response.results.map(({ text_signature }) => text_signature) ); //append new signatures @@ -41,12 +43,13 @@ export async function fetchSignatures(selector: Uint8Array): Promise { async function getSuccessfulResponse( selector: string, + url: string, page: number ): Promise { return await retry( async () => ( - await axios.get("https://www.4byte.directory/api/v1/signatures/", { + await axios.get(`${url}/v1/signatures/`, { params: { hex_signature: selector, page diff --git a/packages/decoder/lib/index.ts b/packages/decoder/lib/index.ts index 94e7110a025..7c3e62172ee 100644 --- a/packages/decoder/lib/index.ts +++ b/packages/decoder/lib/index.ts @@ -195,7 +195,8 @@ export async function forProject( let decoder = new ProjectDecoder( compilations, settings.provider, - ensSettings + ensSettings, + settings.selectorDirectory ); await decoder.init(); return decoder; diff --git a/packages/decoder/lib/types.ts b/packages/decoder/lib/types.ts index c9c4b6dbb52..f89ad1e2a65 100644 --- a/packages/decoder/lib/types.ts +++ b/packages/decoder/lib/types.ts @@ -49,6 +49,12 @@ export interface DecoderSettings { * provider. */ ens?: EnsSettings; + /** + * This field can be included to enable selector-based decoding, using a + * selector directory (such as 4byte.directory) when other + * decoding methods fail. + */ + selectorDirectory?: SelectorDirectorySettings; } //WARNING: copypasted from @truffle/encoder! @@ -72,6 +78,24 @@ export interface EnsSettings { registryAddress?: string; } +/** + * This type contains settings for the use of a selector directory + * when other decoding methods fail. + * @Category inputs + */ +export interface SelectorDirectorySettings { + /** + * Set this to true to enable selector-based decoding. + */ + enabled?: boolean; + /** + * URL for the selector directory API endpoint. It should conform to + * the [4byte.directory API](https://www.4byte.directory/docs/) protocol. + * Defaults to `"https://www.4byte.directory/api"`. + */ + url?: string; +} + /** * This type represents the state of a contract aside from its storage. * @category Results diff --git a/packages/decoder/test/current/test/selector-based-test.js b/packages/decoder/test/current/test/selector-based-test.js index 03c0147a1e2..476fe027cfb 100644 --- a/packages/decoder/test/current/test/selector-based-test.js +++ b/packages/decoder/test/current/test/selector-based-test.js @@ -67,7 +67,8 @@ describe("Selector-based decoding", function () { const decoder = await Decoder.forProject({ provider: web3.currentProvider, - projectInfo: { artifacts: [abstractions.Sink] } + projectInfo: { artifacts: [abstractions.Sink] }, + selectorDirectory: { enabled: true } }); const truffleReceipt = await deployedContract.sendTransaction({ @@ -106,7 +107,8 @@ describe("Selector-based decoding", function () { const decoder = await Decoder.forProject({ provider: web3.currentProvider, - projectInfo: { artifacts: [] } //no info given! + projectInfo: { artifacts: [] }, //no info given! + selectorDirectory: { enabled: true } }); const truffleReceipt = await deployedContract.sendTransaction({ @@ -145,7 +147,8 @@ describe("Selector-based decoding", function () { const decoder = await Decoder.forProject({ provider: web3.currentProvider, - projectInfo: { artifacts: [abstractions.Sink] } + projectInfo: { artifacts: [abstractions.Sink] }, + selectorDirectory: { enabled: true } }); const truffleReceipt = await deployedContract.sendTransaction({ @@ -165,6 +168,32 @@ describe("Selector-based decoding", function () { "selectorBasedDecodings" ); }); + + it("excludes selector-based interpretation when not enabled", async function () { + this.timeout(4000); + const deployedContract = await abstractions.Sink.deployed(); + + const decoder = await Decoder.forProject({ + provider: web3.currentProvider, + projectInfo: { artifacts: [abstractions.Sink] } + }); + + const truffleReceipt = await deployedContract.sendTransaction({ + //selector: 0 + //first argument: 2^255+1 + //second argument: 1 + data: "0x0000000010000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000001" + }); + const tx = await web3.eth.getTransaction(truffleReceipt.tx); + + const overallDecoding = await decoder.decodeTransaction(tx); + + assert.equal(overallDecoding.kind, "message"); + assert.notProperty( + overallDecoding.interpretations, + "selectorBasedDecodings" + ); + }); }); const zeroSelectorResults = {