fix(abigen/solc): make abigen work with ethers-solc and abiencoderv2 (#952)
* feat(solc): add lossless abi * fix(abigen): make abigen work with ethers-solc and abiencoderv2 * chore: update CHANGELOG
This commit is contained in:
parent
9876b49e6c
commit
184cffaca3
|
@ -58,6 +58,8 @@
|
|||
|
||||
### Unreleased
|
||||
|
||||
- Wrap `ethabi::Contract` into new type `LosslessAbi` and `abi: Option<Abi>` with `abi: Option<LosslessAbi>` in `ConfigurableContractArtifact`
|
||||
[#952](https://github.com/gakonst/ethers-rs/pull/952)
|
||||
- Let `Project` take ownership of `ArtifactOutput` and change trait interface
|
||||
[#907](https://github.com/gakonst/ethers-rs/pull/907)
|
||||
- Total revamp of the `Project::compile` pipeline
|
||||
|
|
|
@ -1187,6 +1187,7 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
"dunce",
|
||||
"ethers-core",
|
||||
"ethers-solc",
|
||||
"eyre",
|
||||
"getrandom 0.2.5",
|
||||
"hex",
|
||||
|
|
|
@ -42,3 +42,4 @@ rustls = ["reqwest/rustls-tls"]
|
|||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.2.0"
|
||||
ethers-solc = { path = "../../ethers-solc", default-features = false, features = ["project-util"] }
|
|
@ -173,12 +173,16 @@ impl Context {
|
|||
|
||||
internal_structs
|
||||
} else if abi_str.starts_with('{') {
|
||||
abi_str = serde_json::to_string(&abi).context("fail to serialize abi to json")?;
|
||||
|
||||
serde_json::from_str::<RawAbi>(&abi_str)
|
||||
.ok()
|
||||
.map(InternalStructs::new)
|
||||
.unwrap_or_default()
|
||||
if let Ok(abi) = serde_json::from_str::<RawAbi>(&abi_str) {
|
||||
if let Ok(s) = serde_json::to_string(&abi) {
|
||||
// need to update the `abi_str` here because we only want the `"abi": [...]`
|
||||
// part of the json object in the contract binding
|
||||
abi_str = s;
|
||||
}
|
||||
InternalStructs::new(abi)
|
||||
} else {
|
||||
InternalStructs::default()
|
||||
}
|
||||
} else {
|
||||
serde_json::from_str::<RawAbi>(&abi_str)
|
||||
.ok()
|
||||
|
|
|
@ -238,6 +238,7 @@ impl ContractBindings {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ethers_solc::project_util::TempProject;
|
||||
|
||||
#[test]
|
||||
fn can_generate_structs() {
|
||||
|
@ -247,4 +248,42 @@ mod tests {
|
|||
let out = gen.tokens.to_string();
|
||||
assert!(out.contains("pub struct Stuff"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_compile_and_generate() {
|
||||
let tmp = TempProject::dapptools().unwrap();
|
||||
|
||||
tmp.add_source(
|
||||
"Greeter",
|
||||
r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
contract Greeter {
|
||||
|
||||
struct Inner {
|
||||
bool a;
|
||||
}
|
||||
|
||||
struct Stuff {
|
||||
Inner inner;
|
||||
}
|
||||
|
||||
function greet(Stuff calldata stuff) public view returns (Stuff memory) {
|
||||
return stuff;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = tmp.compile().unwrap();
|
||||
|
||||
let abigen =
|
||||
Abigen::from_file(tmp.artifacts_path().join("Greeter.sol/Greeter.json")).unwrap();
|
||||
let gen = abigen.generate().unwrap();
|
||||
let out = gen.tokens.to_string();
|
||||
assert!(out.contains("pub struct Stuff"));
|
||||
assert!(out.contains("pub struct Inner"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -718,7 +718,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn can_detect_incosistent_single_file_crate() {
|
||||
fn can_detect_inconsistent_single_file_crate() {
|
||||
run_test(|context| {
|
||||
let Context { multi_gen, mod_root } = context;
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ use serde::{
|
|||
};
|
||||
|
||||
/// Contract ABI as a list of items where each item can be a function, constructor or event
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct RawAbi(Vec<Item>);
|
||||
|
||||
impl IntoIterator for RawAbi {
|
||||
|
|
|
@ -4,12 +4,11 @@ use crate::{
|
|||
artifacts::{
|
||||
output_selection::{ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection},
|
||||
CompactBytecode, CompactContract, CompactContractBytecode, CompactDeployedBytecode,
|
||||
CompactEvm, DevDoc, Ewasm, GasEstimates, Metadata, Offsets, Settings, StorageLayout,
|
||||
UserDoc,
|
||||
CompactEvm, DevDoc, Ewasm, GasEstimates, LosslessAbi, Metadata, Offsets, Settings,
|
||||
StorageLayout, UserDoc,
|
||||
},
|
||||
ArtifactOutput, Contract, SolcConfig, SolcError,
|
||||
};
|
||||
use ethers_core::abi::Abi;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
|
||||
|
@ -21,7 +20,7 @@ use std::{collections::BTreeMap, fs, path::Path};
|
|||
pub struct ConfigurableContractArtifact {
|
||||
/// The Ethereum Contract ABI. If empty, it is represented as an empty
|
||||
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
|
||||
pub abi: Option<Abi>,
|
||||
pub abi: Option<LosslessAbi>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub bytecode: Option<CompactBytecode>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
|
@ -74,7 +73,7 @@ impl ConfigurableContractArtifact {
|
|||
impl From<ConfigurableContractArtifact> for CompactContractBytecode {
|
||||
fn from(artifact: ConfigurableContractArtifact) -> Self {
|
||||
CompactContractBytecode {
|
||||
abi: artifact.abi,
|
||||
abi: artifact.abi.map(Into::into),
|
||||
bytecode: artifact.bytecode,
|
||||
deployed_bytecode: artifact.deployed_bytecode,
|
||||
}
|
||||
|
|
|
@ -843,7 +843,7 @@ impl OutputContracts {
|
|||
pub struct Contract {
|
||||
/// The Ethereum Contract Metadata.
|
||||
/// See https://docs.soliditylang.org/en/develop/metadata.html
|
||||
pub abi: Option<Abi>,
|
||||
pub abi: Option<LosslessAbi>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
|
@ -868,6 +868,48 @@ pub struct Contract {
|
|||
pub ir_optimized: Option<String>,
|
||||
}
|
||||
|
||||
/// A helper type that ensures lossless (de)serialisation unlike [`ethabi::Contract`] which omits
|
||||
/// some information of (nested) components in a serde roundtrip. This is a problem for
|
||||
/// abienconderv2 structs because `ethabi::Contract`'s representation of those are [`ethabi::Param`]
|
||||
/// and the `kind` field of type [`ethabi::ParamType`] does not support deeply nested components as
|
||||
/// it's the case for structs. This is not easily fixable in ethabi as it would require a redesign
|
||||
/// of the overall `Param` and `ParamType` types. Instead, this type keeps a copy of the
|
||||
/// [`serde_json::Value`] when deserialized from the `solc` json compiler output and uses it to
|
||||
/// serialize the `abi` without loss.
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct LosslessAbi {
|
||||
/// The complete abi as json value
|
||||
pub abi_value: serde_json::Value,
|
||||
/// The deserialised version of `abi_value`
|
||||
pub abi: Abi,
|
||||
}
|
||||
|
||||
impl From<LosslessAbi> for Abi {
|
||||
fn from(abi: LosslessAbi) -> Self {
|
||||
abi.abi
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for LosslessAbi {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.abi_value.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for LosslessAbi {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let abi_value = serde_json::Value::deserialize(deserializer)?;
|
||||
let abi = serde_json::from_value(abi_value.clone()).map_err(serde::de::Error::custom)?;
|
||||
Ok(Self { abi_value, abi })
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal representation of a contract with a present abi and bytecode.
|
||||
///
|
||||
/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole
|
||||
|
@ -933,7 +975,7 @@ impl From<Contract> for ContractBytecode {
|
|||
(None, None)
|
||||
};
|
||||
|
||||
Self { abi: c.abi, bytecode, deployed_bytecode }
|
||||
Self { abi: c.abi.map(Into::into), bytecode, deployed_bytecode }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -979,7 +1021,7 @@ impl From<Contract> for CompactContractBytecode {
|
|||
(None, None)
|
||||
};
|
||||
|
||||
Self { abi: c.abi, bytecode, deployed_bytecode }
|
||||
Self { abi: c.abi.map(Into::into), bytecode, deployed_bytecode }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1300,7 +1342,7 @@ impl<'a> From<&'a Contract> for CompactContractRef<'a> {
|
|||
(None, None)
|
||||
};
|
||||
|
||||
Self { abi: c.abi.as_ref(), bin, bin_runtime }
|
||||
Self { abi: c.abi.as_ref().map(|abi| &abi.abi), bin, bin_runtime }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
use crate::{
|
||||
artifacts::{
|
||||
Bytecode, BytecodeObject, CompactContract, CompactContractBytecode, Contract,
|
||||
ContractBytecode, DeployedBytecode, Offsets,
|
||||
ContractBytecode, DeployedBytecode, LosslessAbi, Offsets,
|
||||
},
|
||||
ArtifactOutput,
|
||||
};
|
||||
use ethers_core::abi::Abi;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
|
||||
|
@ -24,7 +23,7 @@ pub struct HardhatArtifact {
|
|||
/// The source name of this contract in the workspace like `contracts/Greeter.sol`
|
||||
pub source_name: String,
|
||||
/// The contract's ABI
|
||||
pub abi: Abi,
|
||||
pub abi: LosslessAbi,
|
||||
/// A "0x"-prefixed hex string of the unlinked deployment bytecode. If the contract is not
|
||||
/// deployable, this has the string "0x"
|
||||
pub bytecode: Option<BytecodeObject>,
|
||||
|
@ -44,7 +43,7 @@ pub struct HardhatArtifact {
|
|||
impl From<HardhatArtifact> for CompactContract {
|
||||
fn from(artifact: HardhatArtifact) -> Self {
|
||||
CompactContract {
|
||||
abi: Some(artifact.abi),
|
||||
abi: Some(artifact.abi.abi),
|
||||
bin: artifact.bytecode,
|
||||
bin_runtime: artifact.deployed_bytecode,
|
||||
}
|
||||
|
@ -65,7 +64,7 @@ impl From<HardhatArtifact> for ContractBytecode {
|
|||
bcode.into()
|
||||
});
|
||||
|
||||
ContractBytecode { abi: Some(artifact.abi), bytecode, deployed_bytecode }
|
||||
ContractBytecode { abi: Some(artifact.abi.abi), bytecode, deployed_bytecode }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue