Problem
When inferring ABIs from bytecode, WhatsABI currently omits names for tuple components that are likely derived from Solidity structs. While the ABI specification technically allows unnamed tuple components, Solidity always generates names for struct members when creating the ABI. This discrepancy causes compatibility issues with libraries like go-ethereum that expect these names to be present when working with structs (See https://github.com/ethereum/go-ethereum/blob/v1.14.11/accounts/abi/type.go#L181).
Example contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestC {
struct MyTuple {
uint256 num;
string str;
bool flag;
}
function getTuple() external pure returns (MyTuple memory) {
return MyTuple(123, "hello", true);
}
function setTuple(MyTuple calldata inputTuple) external pure returns (uint256, string memory, bool) {
return (inputTuple.num, inputTuple.str, inputTuple.flag);
}
}
ABI Generated by Solidity Compiler:
{
"contracts": {
"test.sol:TestC": {
"abi": [
{
"inputs": [],
"name": "getTuple",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
},
{
"internalType": "string",
"name": "str",
"type": "string"
},
{
"internalType": "bool",
"name": "flag",
"type": "bool"
}
],
"internalType": "struct TestC.MyTuple",
"name": "",
"type": "tuple"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
},
{
"internalType": "string",
"name": "str",
"type": "string"
},
{
"internalType": "bool",
"name": "flag",
"type": "bool"
}
],
"internalType": "struct TestC.MyTuple",
"name": "inputTuple",
"type": "tuple"
}
],
"name": "setTuple",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "string",
"name": "",
"type": "string"
},
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "pure",
"type": "function"
}
]
}
},
"version": "0.8.25+commit.b61c2a91.Darwin.appleclang"
}
Proposed solution
Fill in the component names with _n when they're empty.
Fill in the component names with field${n} when they're empty.
Example:
{
"type": "function",
"selector": "0x95d376d7",
"payable": false,
"stateMutability": "payable",
"inputs": [
{
"type": "tuple",
"name": "",
"components": [
{ "type": "uint32", "name": "" },
{ "type": "bytes", "name": "" },
{ "type": "bytes32", "name": "" },
{ "type": "uint64", "name": "" },
{ "type": "address", "name": "" }
]
},
{ "type": "bytes", "name": "" }
],
"sig": "assignJob((uint32,bytes,bytes32,uint64,address),bytes)",
"name": "assignJob",
"constant": false
}
To be:
{
"type": "function",
"selector": "0x95d376d7",
"payable": false,
"stateMutability": "payable",
"inputs": [
{
"type": "tuple",
"name": "",
"components": [
{ "type": "uint32", "name": "field0" },
{ "type": "bytes", "name": "field1" },
{ "type": "bytes32", "name": "field2" },
{ "type": "uint64", "name": "field3" },
{ "type": "address", "name": "field4" }
]
},
{ "type": "bytes", "name": "" }
],
"sig": "assignJob((uint32,bytes,bytes32,uint64,address),bytes)",
"name": "assignJob",
"constant": false
}
EDIT: Adjusted the placeholder format that should be supported by geth.
Problem
When inferring ABIs from bytecode, WhatsABI currently omits names for tuple components that are likely derived from Solidity structs. While the ABI specification technically allows unnamed tuple components, Solidity always generates names for struct members when creating the ABI. This discrepancy causes compatibility issues with libraries like go-ethereum that expect these names to be present when working with structs (See https://github.com/ethereum/go-ethereum/blob/v1.14.11/accounts/abi/type.go#L181).
Example contract:
ABI Generated by Solidity Compiler:
{ "contracts": { "test.sol:TestC": { "abi": [ { "inputs": [], "name": "getTuple", "outputs": [ { "components": [ { "internalType": "uint256", "name": "num", "type": "uint256" }, { "internalType": "string", "name": "str", "type": "string" }, { "internalType": "bool", "name": "flag", "type": "bool" } ], "internalType": "struct TestC.MyTuple", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "uint256", "name": "num", "type": "uint256" }, { "internalType": "string", "name": "str", "type": "string" }, { "internalType": "bool", "name": "flag", "type": "bool" } ], "internalType": "struct TestC.MyTuple", "name": "inputTuple", "type": "tuple" } ], "name": "setTuple", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" }, { "internalType": "string", "name": "", "type": "string" }, { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "pure", "type": "function" } ] } }, "version": "0.8.25+commit.b61c2a91.Darwin.appleclang" }Proposed solution
Fill in the component names with_nwhen they're empty.Fill in the component names with
field${n}when they're empty.Example:
{ "type": "function", "selector": "0x95d376d7", "payable": false, "stateMutability": "payable", "inputs": [ { "type": "tuple", "name": "", "components": [ { "type": "uint32", "name": "" }, { "type": "bytes", "name": "" }, { "type": "bytes32", "name": "" }, { "type": "uint64", "name": "" }, { "type": "address", "name": "" } ] }, { "type": "bytes", "name": "" } ], "sig": "assignJob((uint32,bytes,bytes32,uint64,address),bytes)", "name": "assignJob", "constant": false }To be:
{ "type": "function", "selector": "0x95d376d7", "payable": false, "stateMutability": "payable", "inputs": [ { "type": "tuple", "name": "", "components": [ { "type": "uint32", "name": "field0" }, { "type": "bytes", "name": "field1" }, { "type": "bytes32", "name": "field2" }, { "type": "uint64", "name": "field3" }, { "type": "address", "name": "field4" } ] }, { "type": "bytes", "name": "" } ], "sig": "assignJob((uint32,bytes,bytes32,uint64,address),bytes)", "name": "assignJob", "constant": false }EDIT: Adjusted the placeholder format that should be supported by geth.