Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(deps): Move to Alloy ABI encoding/decoding & alloy types #5986

Merged
merged 83 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
ee18e87
feat: find and replace all ethers_solc mentions outside anvil
Evalir Oct 5, 2023
6c6aa4e
chore: keep resolving errors as fuzz is getting migrated
Evalir Oct 5, 2023
f33e414
feat: fuzz/trace changes
Evalir Oct 6, 2023
a8bb5b0
feat: partial trace migration
Evalir Oct 6, 2023
adc2786
chore: use temporal sol! macro in diff file for decode migration
Evalir Oct 17, 2023
8bc67d2
feat: use proptest traits instead of custom impl
Evalir Oct 17, 2023
65f0918
chore: address comments
Evalir Oct 17, 2023
a54005a
chore: remove alloy console bindings
Evalir Oct 17, 2023
2c70bd9
feat: introduce foundry-block-explorers
Evalir Oct 17, 2023
55ad9bf
chore: partial common abi helpers migration
Evalir Oct 17, 2023
94fb974
feat: partial decode migration
Evalir Oct 18, 2023
b3935cc
feat: re-introduce block-explorers
Evalir Oct 18, 2023
8737a52
feat: fix compiler errors
Evalir Oct 18, 2023
988e756
chore
Evalir Oct 18, 2023
5b8c207
chore: tentative inspector migration
Evalir Oct 18, 2023
bd2fbb3
Merge branch 'master' into evalir/introduce-foundry-compilers
Evalir Oct 18, 2023
3c3b35c
feat: switch to using static decoder to decode errors
Evalir Oct 18, 2023
7484d3b
chore: clippy
Evalir Oct 18, 2023
3adebd5
feat: migrate trace types temporarily
Evalir Oct 19, 2023
c44e796
chore: replace ethers tracing types for local tracing types
Evalir Oct 19, 2023
b51f741
chore: rebase
Evalir Oct 19, 2023
bb68371
fix: handle decoding with static decoder, tests
Evalir Oct 19, 2023
079c879
chore: use JsonAbi for console/hardhat/hevm abis
Evalir Oct 19, 2023
d9207b2
chore: add todo
Evalir Oct 19, 2023
efd0323
chore: replace types downstream and remove glue
Evalir Oct 19, 2023
de459d4
feat: fix last evm issues, start fixing downstream type issues
Evalir Oct 20, 2023
8356303
chore: cargo
Evalir Oct 20, 2023
bcb57a2
chore: more downstream error fixes
Evalir Oct 20, 2023
4d4255b
chore: fix test files
Evalir Oct 20, 2023
5f145b7
chore: more downstream fixes
Evalir Oct 20, 2023
90d5b05
chore: fmt
Evalir Oct 20, 2023
3ea011c
feat: migrate unit utils, replace
Evalir Oct 20, 2023
48832e5
chore: fix tests, fmt
Evalir Oct 20, 2023
2cefb6f
compiles
Evalir Oct 22, 2023
6bef6ad
clippy
Evalir Oct 22, 2023
170f5e6
chore: clippy
Evalir Oct 22, 2023
d8e54b9
Merge branch 'master' into evalir/introduce-foundry-compilers
Evalir Oct 22, 2023
5b404e0
chore: last fixes
Evalir Oct 22, 2023
2dbe680
chore: update block explorers
Evalir Oct 22, 2023
ea9b980
chore: actually coerce values correctly
Evalir Oct 23, 2023
a1b7442
chore: fix broken test
Evalir Oct 23, 2023
23e281d
chore: fix estimation test, parse values as alloy nums
Evalir Oct 23, 2023
bdc95bb
chore: fix abi parsing
Evalir Oct 23, 2023
8507f92
chore: selector tests
Evalir Oct 23, 2023
e42aeba
chore: fix more tests, remove more glue
Evalir Oct 23, 2023
dc05ee5
chore: properly decode logs
Evalir Oct 23, 2023
944174a
chore: use selector_type to handle tuples correctly
Evalir Oct 23, 2023
4b2f0ea
chore: clippy and fix another test
Evalir Oct 23, 2023
f297ace
chore: fix remaining abi tests
Evalir Oct 23, 2023
403de57
chore: use proptest traits for fuzzer
Evalir Oct 23, 2023
ffcb558
more test fixes ongod
Evalir Oct 23, 2023
2c64c3d
clippy
Evalir Oct 23, 2023
ef21ace
chore: use abigen for console logs for now
Evalir Oct 23, 2023
99c228d
fix: generate valid values in fuzzer
Evalir Oct 23, 2023
ebf9523
chore: clippy
Evalir Oct 23, 2023
92c6b5a
chore: readd settings
Evalir Oct 23, 2023
e3dfa8b
chore: various fixes
Evalir Oct 23, 2023
da9b483
chore: fix script arguments decoding
Evalir Oct 24, 2023
adc940a
chore: fix more tests
Evalir Oct 24, 2023
b1f5ff7
chore: last ots fixes
Evalir Oct 24, 2023
0dbcf71
fix: decoding
Evalir Oct 24, 2023
0e2c954
chore: clippy
Evalir Oct 24, 2023
4d9ea34
chore: fmt
Evalir Oct 24, 2023
9b5c45c
chore: fix deny check
Evalir Oct 24, 2023
1d1dc01
chore: deny fixes
Evalir Oct 24, 2023
e50cc48
chore: force default features off
Evalir Oct 24, 2023
5ce86fe
Merge branch 'master' into evalir/introduce-foundry-compilers
Evalir Oct 24, 2023
5c37c4b
chore: update block-explorers
Evalir Oct 24, 2023
30b2295
chore: doc fixes
Evalir Oct 24, 2023
cd25d3b
chore: ignore invariant storage test due to flakyness
Evalir Oct 24, 2023
6c88473
chore: update foundry-block-explorers
Evalir Oct 24, 2023
fb7bd39
chore: cleanup, config migration
Evalir Oct 24, 2023
5500c14
chore: resolve comments, more cleanup, remove unwraps
Evalir Oct 24, 2023
e4e347a
chore: remove last mentions of ethers::etherscan
Evalir Oct 24, 2023
823a6e3
chore: remove ethers-solc feat
Evalir Oct 24, 2023
27f107a
Merge branch 'master' into evalir/introduce-foundry-compilers
Evalir Oct 24, 2023
cf7fdce
chore: use alloy master again
Evalir Oct 24, 2023
371e817
chore: readd NameOrAddress
Evalir Oct 24, 2023
4e4e4c1
chore: clippy/fmt
Evalir Oct 24, 2023
bcd565f
chore: readd support on storage
Evalir Oct 24, 2023
bd3e506
fix: add remappings on settings
Evalir Oct 25, 2023
f958758
chore: address comments (remove create2, noop map, remove eyre from d…
Evalir Oct 25, 2023
963234d
chore: use NameOrAddress
Evalir Oct 25, 2023
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
Prev Previous commit
Next Next commit
Merge branch 'master' into evalir/introduce-foundry-compilers
  • Loading branch information
Evalir committed Oct 18, 2023
commit bd2fbb36b97327197a0745dda2d3cfceda609880
147 changes: 96 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,6 @@ ethers-solc = { git = "https://github.com/gakonst/ethers-rs" }
foundry-compilers = {git = "https://github.com/foundry-rs/compilers"}
foundry-block-explorers = {git = "https://github.com/foundry-rs/block-explorers"}

revm = { git = "https://github.com/bluealloy/revm/", branch = "main" }
revm-interpreter = { git = "https://github.com/bluealloy/revm/", branch = "main" }
revm-precompile = { git = "https://github.com/bluealloy/revm/", branch = "main" }
revm-primitives = { git = "https://github.com/bluealloy/revm/", branch = "main" }

alloy-dyn-abi = { git = "https://github.com/alloy-rs/core/", branch = "main"}
alloy-primitives = { git = "https://github.com/alloy-rs/core/", branch = "main"}
alloy-json-abi = { git = "https://github.com/alloy-rs/core/", branch = "main"}
Expand Down
2 changes: 1 addition & 1 deletion crates/abi/src/bindings/mod.rs

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

1 change: 1 addition & 0 deletions crates/cast/bin/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use foundry_cli::{
update_progress, utils,
utils::{handle_traces, TraceResult},
};
use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE};
use foundry_compilers::EvmVersion;
use foundry_config::{find_project_root_path, Config};
use foundry_evm::{
Expand Down
212 changes: 110 additions & 102 deletions crates/common/src/abi.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! ABI related helper functions
use alloy_json_abi::{Function, Event};
use alloy_dyn_abi::{DynSolValue, DynSolType, JsonAbiExt, FunctionExt};
use alloy_primitives::{Address, I256, U256, Log, hex};
use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt};
use alloy_json_abi::{Event, Function};
use alloy_primitives::{hex, Address, Log, I256, U256};
use ethers_core::types::Chain;
use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client};
use eyre::{ContextCompat, Result, WrapErr};
use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client};
use std::{future::Future, pin::Pin, str::FromStr};
use yansi::Paint;

Expand All @@ -19,7 +19,10 @@ pub fn encode_function_args(func: &Function, args: &[impl AsRef<str>]) -> Result
.zip(args)
.map(|(input, arg)| (&input.ty, arg.as_ref()))
.collect::<Vec<_>>();
let args = params.iter().map(|(_, arg)| DynSolValue::from(arg.to_owned().to_string())).collect::<Vec<_>>();
let args = params
.iter()
.map(|(_, arg)| DynSolValue::from(arg.to_owned().to_string()))
.collect::<Vec<_>>();
Evalir marked this conversation as resolved.
Show resolved Hide resolved
Ok(func.abi_encode_input(&args)?)
}

Expand All @@ -28,7 +31,12 @@ pub fn encode_function_args(func: &Function, args: &[impl AsRef<str>]) -> Result
/// # Panics
///
/// If the `sig` is an invalid function signature
Evalir marked this conversation as resolved.
Show resolved Hide resolved
pub fn abi_decode_calldata(sig: &str, calldata: &str, input: bool, fn_selector: bool) -> Result<Vec<DynSolValue>> {
pub fn abi_decode_calldata(
sig: &str,
calldata: &str,
input: bool,
fn_selector: bool,
) -> Result<Vec<DynSolValue>> {
let func = Function::parse(sig)?;
let calldata = hex::decode(calldata)?;
let res = if input {
Expand All @@ -51,53 +59,52 @@ pub fn abi_decode_calldata(sig: &str, calldata: &str, input: bool, fn_selector:
}

/// Parses string input as Token against the expected ParamType
pub fn parse_tokens<'a, I: IntoIterator<Item = (&'a ParamType, &'a str)>>(
params: I,
lenient: bool,
) -> Result<Vec<Token>> {
let mut tokens = Vec::new();

for (param, value) in params.into_iter() {
let mut token = if lenient {
LenientTokenizer::tokenize(param, value)
} else {
StrictTokenizer::tokenize(param, value)
};
if token.is_err() && value.starts_with("0x") {
match param {
ParamType::FixedBytes(32) => {
if value.len() < 66 {
let padded_value = [value, &"0".repeat(66 - value.len())].concat();
token = if lenient {
LenientTokenizer::tokenize(param, &padded_value)
} else {
StrictTokenizer::tokenize(param, &padded_value)
};
}
}
ParamType::Uint(_) => {
// try again if value is hex
if let Ok(value) = U256::from_str(value).map(|v| v.to_string()) {
token = if lenient {
LenientTokenizer::tokenize(param, &value)
} else {
StrictTokenizer::tokenize(param, &value)
};
}
}
// TODO: Not sure what to do here. Put the no effect in for now, but that is not
// ideal. We could attempt massage for every value type?
_ => {}
}
}

let token = token.map(sanitize_token).wrap_err_with(|| {
format!("Failed to parse `{value}`, expected value of type: {param}")
})?;
tokens.push(token);
}
Ok(tokens)
}
// pub fn parse_tokens<'a, I: IntoIterator<Item = (&'a ParamType, &'a str)>>(
// params: I,
// lenient: bool,
// ) -> Result<Vec<Token>> { let mut tokens = Vec::new();

// for (param, value) in params.into_iter() {
// let mut token = if lenient {
// LenientTokenizer::tokenize(param, value)
// } else {
// StrictTokenizer::tokenize(param, value)
// };
// if token.is_err() && value.starts_with("0x") {
// match param {
// ParamType::FixedBytes(32) => {
// if value.len() < 66 {
// let padded_value = [value, &"0".repeat(66 - value.len())].concat();
// token = if lenient {
// LenientTokenizer::tokenize(param, &padded_value)
// } else {
// StrictTokenizer::tokenize(param, &padded_value)
// };
// }
// }
// ParamType::Uint(_) => {
// // try again if value is hex
// if let Ok(value) = U256::from_str(value).map(|v| v.to_string()) {
// token = if lenient {
// LenientTokenizer::tokenize(param, &value)
// } else {
// StrictTokenizer::tokenize(param, &value)
// };
// }
// }
// // TODO: Not sure what to do here. Put the no effect in for now, but that is not
// // ideal. We could attempt massage for every value type?
// _ => {}
// }
// }

// let token = token.map(sanitize_token).wrap_err_with(|| {
// format!("Failed to parse `{value}`, expected value of type: {param}")
// })?;
// tokens.push(token);
// }
// Ok(tokens)
// }

/// Cleans up potential shortcomings of the ethabi Tokenizer.
///
Expand Down Expand Up @@ -172,8 +179,8 @@ pub fn format_token(param: &DynSolValue) -> String {
DynSolValue::Tuple(tokens) => {
let string = tokens.iter().map(format_token).collect::<Vec<String>>().join(", ");
format!("({string})")
},
DynSolValue::Function(_) => unimplemented!()
}
_ => unimplemented!(),
}
}

Expand Down Expand Up @@ -251,8 +258,8 @@ pub fn get_func(sig: &str) -> Result<Function> {
Ok(match Function::parse(sig) {
Ok(func) => func,
Err(err) => {
// we return the `Function` parse error as this case is more likely
return Err(err.into())
// we return the `Function` parse error as this case is more likely
return Err(err.into())
}
})
}
Expand All @@ -268,8 +275,7 @@ pub fn get_indexed_event(mut event: Event, raw_log: &Log) -> Event {
if !event.anonymous && raw_log.topics().len() > 1 {
let indexed_params = raw_log.topics().len() - 1;
let num_inputs = event.inputs.len();
let num_address_params =
event.inputs.iter().filter(|p| p.ty == "address").count();
let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();

event.inputs.iter_mut().enumerate().for_each(|(index, param)| {
if param.name.is_empty() {
Expand Down Expand Up @@ -349,48 +355,48 @@ mod tests {
use alloy_dyn_abi::EventExt;
use alloy_primitives::B256;

#[test]
fn can_sanitize_token() {
let token =
Token::Array(LenientTokenizer::tokenize_array("[\"\"]", &ParamType::String).unwrap());
let sanitized = sanitize_token(token);
assert_eq!(sanitized, Token::Array(vec![Token::String("".to_string())]));

let token =
Token::Array(LenientTokenizer::tokenize_array("['']", &ParamType::String).unwrap());
let sanitized = sanitize_token(token);
assert_eq!(sanitized, Token::Array(vec![Token::String("".to_string())]));

let token = Token::Array(
LenientTokenizer::tokenize_array("[\"\",\"\"]", &ParamType::String).unwrap(),
);
let sanitized = sanitize_token(token);
assert_eq!(
sanitized,
Token::Array(vec![Token::String("".to_string()), Token::String("".to_string())])
);

let token =
Token::Array(LenientTokenizer::tokenize_array("['','']", &ParamType::String).unwrap());
let sanitized = sanitize_token(token);
assert_eq!(
sanitized,
Token::Array(vec![Token::String("".to_string()), Token::String("".to_string())])
);
}

#[test]
fn parse_hex_uint_tokens() {
let param = DynSolType::Uint(256);

let tokens = parse_tokens(std::iter::once((&param, "100")), true).unwrap();
assert_eq!(tokens, vec![DynSolValue::Uint(U256::from(100), 256)]);

let val: U256 = U256::from(100u64);
let hex_val = format!("0x{val:x}");
let tokens = parse_tokens(std::iter::once((&param, hex_val.as_str())), true).unwrap();
assert_eq!(tokens, vec![DynSolValue::Uint(U256::from(100), 256)]);
}
// #[test]
// fn can_sanitize_token() {
// let token =
// Token::Array(LenientTokenizer::tokenize_array("[\"\"]",
// &ParamType::String).unwrap()); let sanitized = sanitize_token(token);
// assert_eq!(sanitized, Token::Array(vec![Token::String("".to_string())]));

// let token =
// Token::Array(LenientTokenizer::tokenize_array("['']", &ParamType::String).unwrap());
// let sanitized = sanitize_token(token);
// assert_eq!(sanitized, Token::Array(vec![Token::String("".to_string())]));

// let token = Token::Array(
// LenientTokenizer::tokenize_array("[\"\",\"\"]", &ParamType::String).unwrap(),
// );
// let sanitized = sanitize_token(token);
// assert_eq!(
// sanitized,
// Token::Array(vec![Token::String("".to_string()), Token::String("".to_string())])
// );

// let token =
// Token::Array(LenientTokenizer::tokenize_array("['','']",
// &ParamType::String).unwrap()); let sanitized = sanitize_token(token);
// assert_eq!(
// sanitized,
// Token::Array(vec![Token::String("".to_string()), Token::String("".to_string())])
// );
// }

// #[test]
// fn parse_hex_uint_tokens() {
// let param = DynSolType::Uint(256);

// let tokens = parse_tokens(std::iter::once((&param, "100")), true).unwrap();
// assert_eq!(tokens, vec![DynSolValue::Uint(U256::from(100), 256)]);

// let val: U256 = U256::from(100u64);
// let hex_val = format!("0x{val:x}");
// let tokens = parse_tokens(std::iter::once((&param, hex_val.as_str())), true).unwrap();
// assert_eq!(tokens, vec![DynSolValue::Uint(U256::from(100), 256)]);
// }

#[test]
fn test_indexed_only_address() {
Expand Down Expand Up @@ -449,7 +455,9 @@ mod tests {
// copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1191.md
let eip1191 = "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359";
assert_ne!(
format_token(&DynSolValue::Address(Address::from_str(&eip1191.to_lowercase()).unwrap())),
format_token(&DynSolValue::Address(
Address::from_str(&eip1191.to_lowercase()).unwrap()
)),
eip1191.to_string()
);
}
Expand Down
16 changes: 14 additions & 2 deletions crates/common/src/calc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ where
return U256::ZERO
}

values.iter().copied().fold(U256::ZERO, |sum, val| sum + val.into()).div(U256::from(values.len()))
values
.iter()
.copied()
.fold(U256::ZERO, |sum, val| sum + val.into())
.div(U256::from(values.len()))
}

/// Returns the median of a _sorted_ slice
Expand Down Expand Up @@ -83,7 +87,15 @@ mod tests {

#[test]
fn calc_mean() {
let values = [U256::ZERO, U256::from(1), U256::from(2u64), U256::from(3u64), U256::from(4u64), U256::from(5u64), U256::from(6u64)];
let values = [
U256::ZERO,
U256::from(1),
U256::from(2u64),
U256::from(3u64),
U256::from(4u64),
U256::from(5u64),
U256::from(6u64),
];
let m = mean(&values);
assert_eq!(m, U256::from(3u64));
}
Expand Down
2 changes: 1 addition & 1 deletion crates/common/src/compile.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Support for compiling [foundry_compilers::Project]
use crate::{compact_to_contract, glob::GlobMatcher, term, TestFunctionExt};
use comfy_table::{presets::ASCII_MARKDOWN, *};
use foundry_block_explorers::contract::Metadata;
use eyre::Result;
use foundry_block_explorers::contract::Metadata;
use foundry_compilers::{
artifacts::{BytecodeObject, ContractBytecodeSome},
remappings::Remapping,
Expand Down
3 changes: 2 additions & 1 deletion crates/config/src/etherscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ impl ResolvedEtherscanConfig {
/// `api_key` and cache
pub fn into_client(
self,
) -> Result<foundry_block_explorers::Client, foundry_block_explorers::errors::EtherscanError> {
) -> Result<foundry_block_explorers::Client, foundry_block_explorers::errors::EtherscanError>
{
let ResolvedEtherscanConfig { api_url, browser_url, key: api_key, chain } = self;
let (mainnet_api, mainnet_url) =
ethers_core::types::Chain::Mainnet.etherscan_urls().expect("exist; qed");
Expand Down
16 changes: 10 additions & 6 deletions crates/evm/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ use crate::{
abi::ConsoleEvents::{self, *},
executor::inspector::cheatcodes::util::MAGIC_SKIP_BYTES,
};
use alloy_dyn_abi::{JsonAbiExt, DynSolValue, DynSolType};
use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt};
use alloy_json_abi::JsonAbi;
use alloy_primitives::{B256, Bytes};
use alloy_primitives::{Log as AlloyLog};
use alloy_primitives::{Bytes, Log as AlloyLog, B256};
use alloy_sol_types::{sol, SolEvent};
use ethers::{
abi::{decode, AbiDecode, Contract as Abi, ParamType, RawLog, Token},
Expand Down Expand Up @@ -55,7 +54,10 @@ pub fn decode_console_logs(logs: &[Log]) -> Vec<String> {
/// This function returns [None] if it is not a DSTest log or the result of a Hardhat
/// `console.log`.
pub fn decode_console_log(log: &Log) -> Option<String> {
let raw_log = AlloyLog::new_unchecked(log.topics.into_iter().map(|h| h.to_alloy()).collect_vec(), log.data.0.into());
let raw_log = AlloyLog::new_unchecked(
log.topics.into_iter().map(|h| h.to_alloy()).collect_vec(),
log.data.0.into(),
);
let data = log_address::abi_decode_data(&raw_log.data, false);
if let Ok(inner) = log::abi_decode_data(&raw_log.data, false) {
return Some(inner.0)
Expand Down Expand Up @@ -215,7 +217,8 @@ pub fn decode_revert(
if abi_error.signature()[..SELECTOR_LEN].as_bytes() == &err[..SELECTOR_LEN] {
// if we don't decode, don't return an error, try to decode as a
// string later
if let Ok(decoded) = abi_error.abi_decode_input(&err[SELECTOR_LEN..], false) {
if let Ok(decoded) = abi_error.abi_decode_input(&err[SELECTOR_LEN..], false)
{
let inputs = decoded
.iter()
.map(foundry_common::abi::format_token)
Expand Down Expand Up @@ -306,7 +309,8 @@ pub fn decode_custom_error_args(err: &[u8], args: usize) -> Option<DynSolValue>
// brute force decode all possible combinations
for num in (2..=args).rev() {
for candidate in TYPES.iter().cloned().combinations(num) {
if let Ok(decoded) = DynSolType::abi_decode_sequence( &DynSolType::Tuple(candidate), err) {
if let Ok(decoded) = DynSolType::abi_decode_sequence(&DynSolType::Tuple(candidate), err)
{
return Some(decoded)
}
}
Expand Down
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.