Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

fix(abigen/solc): make abigen work with ethers-solc and abiencoderv2 #952

Merged
merged 3 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ethers-contract/ethers-contract-abigen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
16 changes: 10 additions & 6 deletions ethers-contract/ethers-contract-abigen/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
39 changes: 39 additions & 0 deletions ethers-contract/ethers-contract-abigen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ impl ContractBindings {
#[cfg(test)]
mod tests {
use super::*;
use ethers_solc::project_util::TempProject;

#[test]
fn can_generate_structs() {
Expand All @@ -244,4 +245,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"));
}
}
2 changes: 1 addition & 1 deletion ethers-contract/ethers-contract-abigen/src/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 2 additions & 1 deletion ethers-contract/ethers-contract-abigen/src/rawabi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 4 additions & 5 deletions ethers-solc/src/artifact_output/configurable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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")]
Expand Down Expand Up @@ -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,
}
Expand Down
50 changes: 46 additions & 4 deletions ethers-solc/src/artifacts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand Down Expand Up @@ -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 }
}
}

Expand Down Expand Up @@ -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 }
}
}

Expand Down Expand Up @@ -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 }
}
}

Expand Down
9 changes: 4 additions & 5 deletions ethers-solc/src/hh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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>,
Expand All @@ -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,
}
Expand All @@ -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 }
}
}

Expand Down