From 4036f45f3d3c762d46ba719c9898b4308bb63cb8 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 30 Sep 2021 11:27:24 +0300 Subject: [PATCH] feat(contract, abigen): support Hardhat ABI format (#478) --- .../src/contract/methods.rs | 11 ++ .../ethers-contract-abigen/src/rawabi.rs | 74 ++++++- .../ethers-contract-abigen/src/source.rs | 9 +- .../verifier_abi_hardhat.json | 185 ++++++++++++++++++ 4 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 ethers-contract/tests/solidity-contracts/verifier_abi_hardhat.json diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 608778e1..fc481eff 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -205,6 +205,7 @@ mod tests { let params = vec![Param { name: "arg_a".to_string(), kind: ParamType::Address, + internal_type: None, }]; let token_stream = expand_inputs_call_arg(¶ms); assert_eq!(token_stream.to_string(), "arg_a"); @@ -214,10 +215,12 @@ mod tests { Param { name: "arg_a".to_string(), kind: ParamType::Address, + internal_type: None, }, Param { name: "arg_b".to_string(), kind: ParamType::Uint(256usize), + internal_type: None, }, ]; let token_stream = expand_inputs_call_arg(¶ms); @@ -228,14 +231,17 @@ mod tests { Param { name: "arg_a".to_string(), kind: ParamType::Address, + internal_type: None, }, Param { name: "arg_b".to_string(), kind: ParamType::Uint(128usize), + internal_type: None, }, Param { name: "arg_c".to_string(), kind: ParamType::Bool, + internal_type: None, }, ]; let token_stream = expand_inputs_call_arg(¶ms); @@ -256,10 +262,12 @@ mod tests { Param { name: "a".to_string(), kind: ParamType::Bool, + internal_type: None, }, Param { name: "b".to_string(), kind: ParamType::Address, + internal_type: None, }, ], ) @@ -279,6 +287,7 @@ mod tests { expand_fn_outputs(&[Param { name: "a".to_string(), kind: ParamType::Bool, + internal_type: None, }]) .unwrap(), { bool }, @@ -292,10 +301,12 @@ mod tests { Param { name: "a".to_string(), kind: ParamType::Bool, + internal_type: None, }, Param { name: "b".to_string(), kind: ParamType::Address, + internal_type: None, }, ],) .unwrap(), diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs index fc04f49d..3904fd68 100644 --- a/ethers-contract/ethers-contract-abigen/src/rawabi.rs +++ b/ethers-contract/ethers-contract-abigen/src/rawabi.rs @@ -1,10 +1,72 @@ //! This is a basic representation of a contract ABI that does no post processing but contains the raw content of the ABI. #![allow(missing_docs)] -use serde::{Deserialize, Serialize}; +use serde::{ + de::{EnumAccess, Error, MapAccess, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; /// Contract ABI as a list of items where each item can be a function, constructor or event -pub type RawAbi = Vec; +pub struct RawAbi(Vec); + +impl IntoIterator for RawAbi { + type Item = Item; + type IntoIter = std::vec::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +struct RawAbiVisitor; + +impl<'de> Visitor<'de> for RawAbiVisitor { + type Value = RawAbi; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence or map with `abi` key") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut vec = Vec::new(); + + while let Some(element) = seq.next_element()? { + vec.push(element); + } + + Ok(RawAbi(vec)) + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut vec = None; + + while let Some(key) = map.next_key::()? { + if key == "abi" { + vec = Some(RawAbi(map.next_value::>()?)); + } else { + map.next_value::()?; + } + } + + vec.ok_or_else(|| serde::de::Error::missing_field("abi")) + } +} + +impl<'de> Deserialize<'de> for RawAbi { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(RawAbiVisitor) + } +} #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -40,9 +102,17 @@ pub struct Component { #[cfg(test)] mod tests { use super::*; + #[test] fn can_parse_raw_abi() { const VERIFIER_ABI: &str = include_str!("../../tests/solidity-contracts/verifier_abi.json"); let _ = serde_json::from_str::(VERIFIER_ABI).unwrap(); } + + #[test] + fn can_parse_hardhat_raw_abi() { + const VERIFIER_ABI: &str = + include_str!("../../tests/solidity-contracts/verifier_abi_hardhat.json"); + let _ = serde_json::from_str::(VERIFIER_ABI).unwrap(); + } } diff --git a/ethers-contract/ethers-contract-abigen/src/source.rs b/ethers-contract/ethers-contract-abigen/src/source.rs index 32f1ad56..71c08745 100644 --- a/ethers-contract/ethers-contract-abigen/src/source.rs +++ b/ethers-contract/ethers-contract-abigen/src/source.rs @@ -63,7 +63,7 @@ impl Source { S: AsRef, { let source = source.as_ref(); - if source.starts_with('[') { + if matches!(source.chars().next(), Some('[' | '{')) { return Ok(Source::String(source.to_owned())); } let root = env::current_dir()?.canonicalize()?; @@ -283,5 +283,12 @@ mod tests { let src = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"name","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"symbol","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"decimals","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#; let parsed = Source::parse(src).unwrap(); assert_eq!(parsed, Source::String(src.to_owned())); + + let hardhat_src = format!( + r#"{{"_format": "hh-sol-artifact-1", "contractName": "Verifier", "sourceName": "contracts/verifier.sol", "abi": {}, "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {{}}, "deployedLinkReferences": {{}}}}"#, + src, + ); + let hardhat_parsed = Source::parse(&hardhat_src).unwrap(); + assert_eq!(hardhat_parsed, Source::String(hardhat_src)); } } diff --git a/ethers-contract/tests/solidity-contracts/verifier_abi_hardhat.json b/ethers-contract/tests/solidity-contracts/verifier_abi_hardhat.json new file mode 100644 index 00000000..cfc92371 --- /dev/null +++ b/ethers-contract/tests/solidity-contracts/verifier_abi_hardhat.json @@ -0,0 +1,185 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "Verifier", + "sourceName": "contracts/verifier.sol", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "input", + "type": "uint256[]" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct Pairing.G1Point", + "name": "A", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct Pairing.G2Point", + "name": "B", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct Pairing.G1Point", + "name": "C", + "type": "tuple" + } + ], + "internalType": "struct Verifier.Proof", + "name": "proof", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct Pairing.G1Point", + "name": "alfa1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct Pairing.G2Point", + "name": "beta2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct Pairing.G2Point", + "name": "gamma2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct Pairing.G2Point", + "name": "delta2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct Pairing.G1Point[]", + "name": "IC", + "type": "tuple[]" + } + ], + "internalType": "struct Verifier.VerifyingKey", + "name": "vk", + "type": "tuple" + } + ], + "name": "verify", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +}