fix(abigen): handle lossy ethabi generated abi structs (#950)

* fix(abigen): handle lossy ethabi generated abi structs

* chore: rustfmt
This commit is contained in:
Matthias Seitz 2022-02-22 19:26:21 +01:00 committed by GitHub
parent 60515d9404
commit 45a37faa3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 107 additions and 19 deletions

View File

@ -91,23 +91,43 @@ impl Context {
tuple: ParamType,
) -> Result<TokenStream> {
let mut fields = Vec::with_capacity(sol_struct.fields().len());
// determines whether we have enough info to create named fields
let is_tuple = sol_struct.has_nameless_field();
for field in sol_struct.fields() {
let field_name = util::ident(&field.name().to_snake_case());
match field.r#type() {
FieldType::Elementary(ty) => {
let ty = types::expand(ty)?;
fields.push(quote! { pub #field_name: #ty });
}
FieldType::Struct(struct_ty) => {
let ty = expand_struct_type(struct_ty);
fields.push(quote! { pub #field_name: #ty });
}
let ty = match field.r#type() {
FieldType::Elementary(ty) => types::expand(ty)?,
FieldType::Struct(struct_ty) => expand_struct_type(struct_ty),
FieldType::Mapping(_) => {
eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field)
}
};
if is_tuple {
fields.push(ty);
} else {
let field_name = util::ident(&field.name().to_snake_case());
fields.push(quote! { pub #field_name: #ty });
}
}
let name = util::ident(name);
let struct_def = if is_tuple {
quote! {
pub struct #name(
#( #fields ),*
);
}
} else {
quote! {
pub struct #name {
#( #fields ),*
}
}
};
let sig = if let ParamType::Tuple(ref tokens) = tuple {
tokens.iter().map(|kind| kind.to_string()).collect::<Vec<_>>().join(",")
} else {
@ -118,8 +138,6 @@ impl Context {
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
let name = util::ident(name);
// use the same derives as for events
let derives = util::expand_derives(&self.event_derives);
@ -127,9 +145,7 @@ impl Context {
Ok(quote! {
#abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
pub struct #name {
#( #fields ),*
}
#struct_def
})
}

View File

@ -231,3 +231,17 @@ impl ContractBindings {
name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_generate_structs() {
let greeter = include_str!("../../tests/solidity-contracts/greeter_with_struct.json");
let abigen = Abigen::new("Greeter", greeter).unwrap();
let gen = abigen.generate().unwrap();
let out = gen.tokens.to_string();
assert!(out.contains("pub struct Stuff"));
}
}

View File

@ -502,7 +502,7 @@ mod tests {
fn run_test<T>(test: T)
where
T: FnOnce(&Context) -> () + panic::UnwindSafe,
T: FnOnce(&Context) + panic::UnwindSafe,
{
let crate_root = std::path::Path::new(&env!("CARGO_MANIFEST_DIR")).to_owned();
let console = Abigen::new(

View File

@ -8,6 +8,7 @@ use serde::{
};
/// Contract ABI as a list of items where each item can be a function, constructor or event
#[derive(Debug, Clone)]
pub struct RawAbi(Vec<Item>);
impl IntoIterator for RawAbi {
@ -84,11 +85,12 @@ pub struct Item {
pub outputs: Vec<Component>,
}
/// Either
/// Either an input/output or a nested component of an input/output
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Component {
#[serde(rename = "internalType", default, skip_serializing_if = "Option::is_none")]
pub internal_type: Option<String>,
#[serde(default)]
pub name: String,
#[serde(rename = "type")]
pub type_field: String,
@ -112,4 +114,12 @@ mod tests {
include_str!("../../tests/solidity-contracts/verifier_abi_hardhat.json");
let _ = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
}
/// due to ethabi's limitations some may be stripped when ethers-solc generates the abi, such as
/// the name of the component
#[test]
fn can_parse_ethers_solc_generated_abi() {
let s = r#"[{"type":"function","name":"greet","inputs":[{"internalType":"struct Greeter.Stuff","name":"stuff","type":"tuple","components":[{"type":"bool"}]}],"outputs":[{"internalType":"struct Greeter.Stuff","name":"","type":"tuple","components":[{"type":"bool"}]}],"stateMutability":"view"}]"#;
let _ = serde_json::from_str::<RawAbi>(s).unwrap();
}
}

View File

@ -190,7 +190,7 @@ mod tests {
#[test]
fn parse_address_missing_prefix() {
assert!(
!parse_address("0000000000000000000000000000000000000000").is_ok(),
parse_address("0000000000000000000000000000000000000000").is_err(),
"parsing address not starting with 0x should fail"
);
}
@ -198,7 +198,7 @@ mod tests {
#[test]
fn parse_address_address_too_short() {
assert!(
!parse_address("0x00000000000000").is_ok(),
parse_address("0x00000000000000").is_err(),
"parsing address not starting with 0x should fail"
);
}

View File

@ -0,0 +1,43 @@
{
"abi": [
{
"type": "function",
"name": "greet",
"inputs": [
{
"internalType": "struct Greeter.Stuff",
"name": "stuff",
"type": "tuple",
"components": [
{
"type": "bool"
}
]
}
],
"outputs": [
{
"internalType": "struct Greeter.Stuff",
"name": "",
"type": "tuple",
"components": [
{
"type": "bool"
}
]
}
],
"stateMutability": "view"
}
],
"bytecode": {
"object": "0x608060405234801561001057600080fd5b50610242806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80635581701b14610030575b600080fd5b61004a60048036038101906100459190610199565b610060565b60405161005791906101f1565b60405180910390f35b610068610070565b819050919050565b60405180602001604052806000151581525090565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100e282610099565b810181811067ffffffffffffffff82111715610101576101006100aa565b5b80604052505050565b6000610114610085565b905061012082826100d9565b919050565b60008115159050919050565b61013a81610125565b811461014557600080fd5b50565b60008135905061015781610131565b92915050565b60006020828403121561017357610172610094565b5b61017d602061010a565b9050600061018d84828501610148565b60008301525092915050565b6000602082840312156101af576101ae61008f565b5b60006101bd8482850161015d565b91505092915050565b6101cf81610125565b82525050565b6020820160008201516101eb60008501826101c6565b50505050565b600060208201905061020660008301846101d5565b9291505056fea2646970667358221220890202b0964477379a457ab3725a21d7c14581e4596552e32a54e23f1c6564e064736f6c634300080c0033",
"sourceMap": "58:158:0:-:0;;;;;;;;;;;;;;;;;;;",
"linkReferences": {}
},
"deployedBytecode": {
"object": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c80635581701b14610030575b600080fd5b61004a60048036038101906100459190610199565b610060565b60405161005791906101f1565b60405180910390f35b610068610070565b819050919050565b60405180602001604052806000151581525090565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100e282610099565b810181811067ffffffffffffffff82111715610101576101006100aa565b5b80604052505050565b6000610114610085565b905061012082826100d9565b919050565b60008115159050919050565b61013a81610125565b811461014557600080fd5b50565b60008135905061015781610131565b92915050565b60006020828403121561017357610172610094565b5b61017d602061010a565b9050600061018d84828501610148565b60008301525092915050565b6000602082840312156101af576101ae61008f565b5b60006101bd8482850161015d565b91505092915050565b6101cf81610125565b82525050565b6020820160008201516101eb60008501826101c6565b50505050565b600060208201905061020660008301846101d5565b9291505056fea2646970667358221220890202b0964477379a457ab3725a21d7c14581e4596552e32a54e23f1c6564e064736f6c634300080c0033",
"sourceMap": "58:158:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;115:99;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;171:12;;:::i;:::-;202:5;195:12;;115:99;;;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;:::o;7:75:1:-;40:6;73:2;67:9;57:19;;7:75;:::o;88:117::-;197:1;194;187:12;334:117;443:1;440;433:12;457:102;498:6;549:2;545:7;540:2;533:5;529:14;525:28;515:38;;457:102;;;:::o;565:180::-;613:77;610:1;603:88;710:4;707:1;700:15;734:4;731:1;724:15;751:281;834:27;856:4;834:27;:::i;:::-;826:6;822:40;964:6;952:10;949:22;928:18;916:10;913:34;910:62;907:88;;;975:18;;:::i;:::-;907:88;1015:10;1011:2;1004:22;794:238;751:281;;:::o;1038:129::-;1072:6;1099:20;;:::i;:::-;1089:30;;1128:33;1156:4;1148:6;1128:33;:::i;:::-;1038:129;;;:::o;1296:90::-;1330:7;1373:5;1366:13;1359:21;1348:32;;1296:90;;;:::o;1392:116::-;1462:21;1477:5;1462:21;:::i;:::-;1455:5;1452:32;1442:60;;1498:1;1495;1488:12;1442:60;1392:116;:::o;1514:133::-;1557:5;1595:6;1582:20;1573:29;;1611:30;1635:5;1611:30;:::i;:::-;1514:133;;;;:::o;1681:405::-;1750:5;1794:4;1782:9;1777:3;1773:19;1769:30;1766:117;;;1802:79;;:::i;:::-;1766:117;1901:21;1917:4;1901:21;:::i;:::-;1892:30;;1981:1;2021:46;2063:3;2054:6;2043:9;2039:22;2021:46;:::i;:::-;2014:4;2007:5;2003:16;1996:72;1932:147;1681:405;;;;:::o;2092:369::-;2171:6;2220:2;2208:9;2199:7;2195:23;2191:32;2188:119;;;2226:79;;:::i;:::-;2188:119;2346:1;2371:73;2436:7;2427:6;2416:9;2412:22;2371:73;:::i;:::-;2361:83;;2317:137;2092:369;;;;:::o;2467:99::-;2538:21;2553:5;2538:21;:::i;:::-;2533:3;2526:34;2467:99;;:::o;2624:317::-;2761:4;2756:3;2752:14;2848:4;2841:5;2837:16;2831:23;2867:57;2918:4;2913:3;2909:14;2895:12;2867:57;:::i;:::-;2776:158;2730:211;2624:317;;:::o;2947:302::-;3080:4;3118:2;3107:9;3103:18;3095:26;;3131:111;3239:1;3228:9;3224:17;3215:6;3131:111;:::i;:::-;2947:302;;;;:::o",
"linkReferences": {}
}
}

View File

@ -280,6 +280,11 @@ impl SolStruct {
&self.fields
}
/// Returns `true` if a field with an empty name exists
pub fn has_nameless_field(&self) -> bool {
self.fields.iter().any(|f| f.name.is_empty())
}
/// If the struct only consists of elementary fields, this will return `ParamType::Tuple` with
/// all those fields
pub fn as_tuple(&self) -> Option<ParamType> {