Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Allow decoder to use 4byte.directory to decode #6057

Merged
merged 8 commits into from Jun 15, 2023
33 changes: 30 additions & 3 deletions packages/codec/lib/core.ts
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to document somewhere what this option does, since it kind of turns this function into something of a chimera. Like, how is it used for selector-based decoding?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I mean all of this stuff doesn't really have documentation, as this is codec rather than decoder. But I can expand the comment to explain it better.

): Generator<DecoderRequest, CalldataDecoding, Uint8Array> {
const context = info.currentContext;
if (context === null) {
Expand Down Expand Up @@ -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 (
Expand All @@ -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),
Expand All @@ -174,6 +177,30 @@ 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 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");
throw new StopDecodingError({
kind: "ReEncodingMismatchError" as const,
data: encodedData,
reEncodedData
});
}
}
if (isConstructor) {
return {
kind: "constructor" as const,
Expand Down
16 changes: 15 additions & 1 deletion packages/codec/lib/format/errors.ts
Expand Up @@ -979,7 +979,8 @@ export interface OverlargePointersNotImplementedError {
*/
export type InternalUseError =
| OverlongArrayOrStringStrictModeError
| InternalFunctionInABIError;
| InternalFunctionInABIError
| ReEncodingMismatchError;

/**
* Error for the stricter length check in strict mode
Expand All @@ -1000,3 +1001,16 @@ export interface OverlongArrayOrStringStrictModeError {
export interface InternalFunctionInABIError {
kind: "InternalFunctionInABIError";
}

/**
* This doesn't really belong here, but we need something for
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to say "This isn't so much a Format error", rather than "here", since the docstring might not show up in the context you expect always

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good point.

* 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;
}
26 changes: 22 additions & 4 deletions packages/codec/lib/types.ts
Expand Up @@ -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[];
};
}

/**
Expand All @@ -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[];
};
}

/**
Expand Down