I have an interface in solidity which looks like so, I would like my call to revert if the calldata passed isn't of a specific type
// Resolver interface
interface IResolver {
// Pass payment info as calldata only the manager should have the right to update it
function resolve(uint256 amount, ResolverOptions calldata resolverOptions) external returns (uint256);
// Reverts if the calldata passes is not a proper struct
function validateAdditionalCalldata(bytes calldata additionalCalldata) external view;
}
I've created a class to implement this here:
struct fooResolverOptions {
address[] fooAddresses;
uint256[] fooAmounts;
}
contract FooResolver is IResolver {
// Validate the additional calldata passed to the resolver contract
function validateAdditionalCalldata(bytes calldata additionalCalldata) view external {
// Convert the additional calldata to bytes memory
bytes memory additionalCalldataMemory = additionalCalldata;
// Decode the additional calldata as a FooResolverOptions struct
FooResolverOptions memory fooOptions;
bool success = abi.decode(additionalCalldataMemory, fooOptions);
// Check if the decode was successful
require(success, "Invalid additional calldata");
}
}
None of the Way's I've tried to decode work:
bool success = abi.decode(additionalCalldataMemory, fooOptions);
this way claims there is no return value from decode.
FooResolverOptions memory fooOptions;
abi.decode(additionalCalldata, fooOptions);
This way claims it wants a tuple of types. How do I decode a struct data, and validate it succeeded?
Solidity currently (v0.8) doesn't support dynamic arguments in abi.decode()
, so you'll need to write logic that validates against predefined set of types.
bytes calldata additionalCalldata
in your example is an array of bytes, so abi.decode(additionalCalldataMemory, <types>);
tries to decode the binary to whatever <types>
you pass. If the input fits the type length, it will simply decode the value to the type.
Example where the value fits into both bool
and address
types, so both operations succeed:
function validateAdditionalCalldata() pure external returns (bool, address) {
bytes memory additionalCalldataMemory = hex"0000000000000000000000000000000000000000000000000000000000000001";
bool decoded1 = abi.decode(additionalCalldataMemory, (bool));
address decoded2 = abi.decode(additionalCalldataMemory, (address));
return (decoded1, decoded2);
}
When the value doesn't fit the type, it throws an exception. Uncaught exception effectively reverts the transaction or the call. However, you can use try
/ catch
to catch the exception.
pragma solidity ^0.8;
contract FooResolver {
function validateAdditionalCalldata() external view returns (bool, address) {
// does not fit `bool` but still fits `address`
bytes memory additionalCalldataMemory = hex"0000000000000000000000000000000000000000000000000000000000000002";
bool decoded1;
try this.decodeToBool(additionalCalldataMemory) returns (bool decodedValue) {
decoded1 = decodedValue;
} catch {
decoded1 = false;
}
address decoded2 = abi.decode(additionalCalldataMemory, (address));
return (decoded1, decoded2);
}
// workaround - try/catch can be currently (v0.8) used on external function calls - but not on native function calls
function decodeToBool(bytes memory data) external pure returns (bool) {
return abi.decode(data, (bool));
}
}