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
|
### 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
|
- Let `Project` take ownership of `ArtifactOutput` and change trait interface
|
||||||
[#907](https://github.com/gakonst/ethers-rs/pull/907)
|
[#907](https://github.com/gakonst/ethers-rs/pull/907)
|
||||||
- Total revamp of the `Project::compile` pipeline
|
- Total revamp of the `Project::compile` pipeline
|
||||||
|
|
|
@ -1187,6 +1187,7 @@ dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"dunce",
|
"dunce",
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
|
"ethers-solc",
|
||||||
"eyre",
|
"eyre",
|
||||||
"getrandom 0.2.5",
|
"getrandom 0.2.5",
|
||||||
"hex",
|
"hex",
|
||||||
|
|
|
@ -42,3 +42,4 @@ rustls = ["reqwest/rustls-tls"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
|
ethers-solc = { path = "../../ethers-solc", default-features = false, features = ["project-util"] }
|
|
@ -173,12 +173,16 @@ impl Context {
|
||||||
|
|
||||||
internal_structs
|
internal_structs
|
||||||
} else if abi_str.starts_with('{') {
|
} else if abi_str.starts_with('{') {
|
||||||
abi_str = serde_json::to_string(&abi).context("fail to serialize abi to json")?;
|
if let Ok(abi) = serde_json::from_str::<RawAbi>(&abi_str) {
|
||||||
|
if let Ok(s) = serde_json::to_string(&abi) {
|
||||||
serde_json::from_str::<RawAbi>(&abi_str)
|
// need to update the `abi_str` here because we only want the `"abi": [...]`
|
||||||
.ok()
|
// part of the json object in the contract binding
|
||||||
.map(InternalStructs::new)
|
abi_str = s;
|
||||||
.unwrap_or_default()
|
}
|
||||||
|
InternalStructs::new(abi)
|
||||||
|
} else {
|
||||||
|
InternalStructs::default()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
serde_json::from_str::<RawAbi>(&abi_str)
|
serde_json::from_str::<RawAbi>(&abi_str)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
|
@ -238,6 +238,7 @@ impl ContractBindings {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use ethers_solc::project_util::TempProject;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_generate_structs() {
|
fn can_generate_structs() {
|
||||||
|
@ -247,4 +248,42 @@ mod tests {
|
||||||
let out = gen.tokens.to_string();
|
let out = gen.tokens.to_string();
|
||||||
assert!(out.contains("pub struct Stuff"));
|
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]
|
#[test]
|
||||||
fn can_detect_incosistent_single_file_crate() {
|
fn can_detect_inconsistent_single_file_crate() {
|
||||||
run_test(|context| {
|
run_test(|context| {
|
||||||
let Context { multi_gen, mod_root } = 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
|
/// 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>);
|
pub struct RawAbi(Vec<Item>);
|
||||||
|
|
||||||
impl IntoIterator for RawAbi {
|
impl IntoIterator for RawAbi {
|
||||||
|
|
|
@ -4,12 +4,11 @@ use crate::{
|
||||||
artifacts::{
|
artifacts::{
|
||||||
output_selection::{ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection},
|
output_selection::{ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection},
|
||||||
CompactBytecode, CompactContract, CompactContractBytecode, CompactDeployedBytecode,
|
CompactBytecode, CompactContract, CompactContractBytecode, CompactDeployedBytecode,
|
||||||
CompactEvm, DevDoc, Ewasm, GasEstimates, Metadata, Offsets, Settings, StorageLayout,
|
CompactEvm, DevDoc, Ewasm, GasEstimates, LosslessAbi, Metadata, Offsets, Settings,
|
||||||
UserDoc,
|
StorageLayout, UserDoc,
|
||||||
},
|
},
|
||||||
ArtifactOutput, Contract, SolcConfig, SolcError,
|
ArtifactOutput, Contract, SolcConfig, SolcError,
|
||||||
};
|
};
|
||||||
use ethers_core::abi::Abi;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::BTreeMap, fs, path::Path};
|
use std::{collections::BTreeMap, fs, path::Path};
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ use std::{collections::BTreeMap, fs, path::Path};
|
||||||
pub struct ConfigurableContractArtifact {
|
pub struct ConfigurableContractArtifact {
|
||||||
/// The Ethereum Contract ABI. If empty, it is represented as an empty
|
/// The Ethereum Contract ABI. If empty, it is represented as an empty
|
||||||
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
|
/// 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")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub bytecode: Option<CompactBytecode>,
|
pub bytecode: Option<CompactBytecode>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
@ -74,7 +73,7 @@ impl ConfigurableContractArtifact {
|
||||||
impl From<ConfigurableContractArtifact> for CompactContractBytecode {
|
impl From<ConfigurableContractArtifact> for CompactContractBytecode {
|
||||||
fn from(artifact: ConfigurableContractArtifact) -> Self {
|
fn from(artifact: ConfigurableContractArtifact) -> Self {
|
||||||
CompactContractBytecode {
|
CompactContractBytecode {
|
||||||
abi: artifact.abi,
|
abi: artifact.abi.map(Into::into),
|
||||||
bytecode: artifact.bytecode,
|
bytecode: artifact.bytecode,
|
||||||
deployed_bytecode: artifact.deployed_bytecode,
|
deployed_bytecode: artifact.deployed_bytecode,
|
||||||
}
|
}
|
||||||
|
|
|
@ -843,7 +843,7 @@ impl OutputContracts {
|
||||||
pub struct Contract {
|
pub struct Contract {
|
||||||
/// The Ethereum Contract Metadata.
|
/// The Ethereum Contract Metadata.
|
||||||
/// See https://docs.soliditylang.org/en/develop/metadata.html
|
/// See https://docs.soliditylang.org/en/develop/metadata.html
|
||||||
pub abi: Option<Abi>,
|
pub abi: Option<LosslessAbi>,
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
|
@ -868,6 +868,48 @@ pub struct Contract {
|
||||||
pub ir_optimized: Option<String>,
|
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.
|
/// Minimal representation of a contract with a present abi and bytecode.
|
||||||
///
|
///
|
||||||
/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole
|
/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole
|
||||||
|
@ -933,7 +975,7 @@ impl From<Contract> for ContractBytecode {
|
||||||
(None, None)
|
(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)
|
(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)
|
(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::{
|
use crate::{
|
||||||
artifacts::{
|
artifacts::{
|
||||||
Bytecode, BytecodeObject, CompactContract, CompactContractBytecode, Contract,
|
Bytecode, BytecodeObject, CompactContract, CompactContractBytecode, Contract,
|
||||||
ContractBytecode, DeployedBytecode, Offsets,
|
ContractBytecode, DeployedBytecode, LosslessAbi, Offsets,
|
||||||
},
|
},
|
||||||
ArtifactOutput,
|
ArtifactOutput,
|
||||||
};
|
};
|
||||||
use ethers_core::abi::Abi;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::btree_map::BTreeMap;
|
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`
|
/// The source name of this contract in the workspace like `contracts/Greeter.sol`
|
||||||
pub source_name: String,
|
pub source_name: String,
|
||||||
/// The contract's ABI
|
/// 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
|
/// A "0x"-prefixed hex string of the unlinked deployment bytecode. If the contract is not
|
||||||
/// deployable, this has the string "0x"
|
/// deployable, this has the string "0x"
|
||||||
pub bytecode: Option<BytecodeObject>,
|
pub bytecode: Option<BytecodeObject>,
|
||||||
|
@ -44,7 +43,7 @@ pub struct HardhatArtifact {
|
||||||
impl From<HardhatArtifact> for CompactContract {
|
impl From<HardhatArtifact> for CompactContract {
|
||||||
fn from(artifact: HardhatArtifact) -> Self {
|
fn from(artifact: HardhatArtifact) -> Self {
|
||||||
CompactContract {
|
CompactContract {
|
||||||
abi: Some(artifact.abi),
|
abi: Some(artifact.abi.abi),
|
||||||
bin: artifact.bytecode,
|
bin: artifact.bytecode,
|
||||||
bin_runtime: artifact.deployed_bytecode,
|
bin_runtime: artifact.deployed_bytecode,
|
||||||
}
|
}
|
||||||
|
@ -65,7 +64,7 @@ impl From<HardhatArtifact> for ContractBytecode {
|
||||||
bcode.into()
|
bcode.into()
|
||||||
});
|
});
|
||||||
|
|
||||||
ContractBytecode { abi: Some(artifact.abi), bytecode, deployed_bytecode }
|
ContractBytecode { abi: Some(artifact.abi.abi), bytecode, deployed_bytecode }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue