diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs index 305db7ee..d57c8ea5 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs @@ -91,23 +91,43 @@ impl Context { tuple: ParamType, ) -> Result { 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::>().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 }) } diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index a9721b20..a93f1aa3 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -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")); + } +} diff --git a/ethers-contract/ethers-contract-abigen/src/multi.rs b/ethers-contract/ethers-contract-abigen/src/multi.rs index 6afd6270..6b3c3662 100644 --- a/ethers-contract/ethers-contract-abigen/src/multi.rs +++ b/ethers-contract/ethers-contract-abigen/src/multi.rs @@ -502,7 +502,7 @@ mod tests { fn run_test(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( diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs index ce9c62bb..6c66b6f0 100644 --- a/ethers-contract/ethers-contract-abigen/src/rawabi.rs +++ b/ethers-contract/ethers-contract-abigen/src/rawabi.rs @@ -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); impl IntoIterator for RawAbi { @@ -84,11 +85,12 @@ pub struct Item { pub outputs: Vec, } -/// 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, + #[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::(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::(s).unwrap(); + } } diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs index 4c9b6657..b13b5772 100644 --- a/ethers-contract/ethers-contract-abigen/src/util.rs +++ b/ethers-contract/ethers-contract-abigen/src/util.rs @@ -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" ); } diff --git a/ethers-contract/tests/solidity-contracts/greeter_with_struct.json b/ethers-contract/tests/solidity-contracts/greeter_with_struct.json new file mode 100644 index 00000000..4c80ddc1 --- /dev/null +++ b/ethers-contract/tests/solidity-contracts/greeter_with_struct.json @@ -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": {} + } +} \ No newline at end of file diff --git a/ethers-core/src/abi/struct_def.rs b/ethers-core/src/abi/struct_def.rs index 713fafad..d9545aca 100644 --- a/ethers-core/src/abi/struct_def.rs +++ b/ethers-core/src/abi/struct_def.rs @@ -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 {