Skip to content

Commit

Permalink
refactor: remove loading from disk for get code
Browse files Browse the repository at this point in the history
  • Loading branch information
agostbiro committed Sep 9, 2024
1 parent d57b367 commit 3ba3eb4
Show file tree
Hide file tree
Showing 17 changed files with 91 additions and 317 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion crates/edr_napi/src/solidity_tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ impl TryFrom<SolidityTestRunnerConfigArgs> for SolidityTestRunnerConfig {
.map(TryFrom::try_from)
.transpose()?
.unwrap_or_default(),
unchecked_cheatcode_artifacts: false,
fs_permissions: FsPermissions::new(
fs_permissions
.unwrap_or_default()
Expand Down
1 change: 0 additions & 1 deletion crates/foundry/cheatcodes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ foundry-config.workspace = true
foundry-evm-core.workspace = true

alloy-dyn-abi.workspace = true
alloy-json-abi.workspace = true
alloy-primitives.workspace = true
alloy-genesis.workspace = true
alloy-sol-types.workspace = true
Expand Down
25 changes: 5 additions & 20 deletions crates/foundry/cheatcodes/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,8 @@ pub struct CheatsConfig {
pub evm_opts: EvmOpts,
/// Address labels from config
pub labels: HashMap<Address, String>,
/// Artifacts which are guaranteed to be fresh (either recompiled or
/// cached). If Some, `vm.getDeployedCode` invocations are validated to
/// be in scope of this list. If None, no validation is performed.
pub available_artifacts: Option<Arc<ContractsByArtifact>>,
/// Solidity compilation artifacts.
pub available_artifacts: Arc<ContractsByArtifact>,
/// Version of the script/test contract which is currently running.
pub running_version: Option<Version>,
}
Expand All @@ -69,10 +67,6 @@ pub struct CheatsConfigOptions {
/// RPC storage caching settings determines what chains and endpoints to
/// cache
pub rpc_storage_caching: StorageCachingConfig,
/// Whether to enable safety checks for `vm.getCode` and
/// `vm.getDeployedCode` invocations. If disabled, it is possible to
/// access artifacts which were not recompiled or cached.
pub unchecked_cheatcode_artifacts: bool,
/// Configures the permissions of cheat codes that touch the file system.
///
/// This includes what operations can be executed (read, write)
Expand All @@ -89,26 +83,18 @@ impl CheatsConfig {
project_root: PathBuf,
config: CheatsConfigOptions,
evm_opts: EvmOpts,
available_artifacts: Option<Arc<ContractsByArtifact>>,
available_artifacts: Arc<ContractsByArtifact>,
running_version: Option<Version>,
) -> Self {
let CheatsConfigOptions {
rpc_endpoints,
rpc_cache_path,
unchecked_cheatcode_artifacts,
prompt_timeout,
rpc_storage_caching,
fs_permissions,
labels,
} = config;

// If user explicitly disabled safety checks, do not set available_artifacts
let available_artifacts = if unchecked_cheatcode_artifacts {
None
} else {
available_artifacts
};

let fs_permissions = fs_permissions.joined(&project_root);

Self {
Expand Down Expand Up @@ -266,7 +252,7 @@ impl Default for CheatsConfig {
project_root: PathBuf::default(),
evm_opts: EvmOpts::default(),
labels: HashMap::default(),
available_artifacts: Option::default(),
available_artifacts: Arc::<ContractsByArtifact>::default(),
running_version: Option::default(),
}
}
Expand All @@ -283,7 +269,6 @@ mod tests {
rpc_endpoints: RpcEndpoints::default(),
rpc_cache_path: None,
rpc_storage_caching: StorageCachingConfig::default(),
unchecked_cheatcode_artifacts: false,
fs_permissions,
prompt_timeout: 0,
labels: HashMap::default(),
Expand All @@ -293,7 +278,7 @@ mod tests {
PathBuf::from(root),
cheats_config_options,
EvmOpts::default(),
None,
Arc::<ContractsByArtifact>::default(),
None,
)
}
Expand Down
179 changes: 75 additions & 104 deletions crates/foundry/cheatcodes/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use std::{
};

use alloy_dyn_abi::DynSolType;
use alloy_json_abi::ContractObject;
use alloy_primitives::{Bytes, U256};
use alloy_sol_types::SolValue;
use dialoguer::{Input, Password};
Expand Down Expand Up @@ -302,7 +301,7 @@ impl Cheatcode for getDeployedCodeCall {
}
}

/// Returns the path to the json artifact depending on the input
/// Returns the artifact code from known artifacts
///
/// Can parse following input formats:
/// - `path/to/artifact.json`
Expand All @@ -313,121 +312,91 @@ impl Cheatcode for getDeployedCodeCall {
/// - `ContractName`
/// - `ContractName:0.8.23`
fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result<Bytes> {
let path = if path.ends_with(".json") {
PathBuf::from(path)
} else {
let mut parts = path.split(':');

let mut file = None;
let mut contract_name = None;
let mut version = None;

let path_or_name = parts.next().unwrap();
if path_or_name.ends_with(".sol") {
file = Some(PathBuf::from(path_or_name));
if let Some(name_or_version) = parts.next() {
if name_or_version.contains('.') {
version = Some(name_or_version);
} else {
contract_name = Some(name_or_version);
version = parts.next();
}
let mut parts = path.split(':');

let mut file = None;
let mut contract_name = None;
let mut version = None;

let path_or_name = parts.next().unwrap();
if path_or_name.ends_with(".sol") {
file = Some(PathBuf::from(path_or_name));
if let Some(name_or_version) = parts.next() {
if name_or_version.contains('.') {
version = Some(name_or_version);
} else {
contract_name = Some(name_or_version);
version = parts.next();
}
} else {
contract_name = Some(path_or_name);
version = parts.next();
}
} else {
contract_name = Some(path_or_name);
version = parts.next();
}

let version = if let Some(version) = version {
Some(Version::parse(version).map_err(|_err| fmt_err!("Error parsing version"))?)
} else {
None
};

// Use available artifacts list if present
if let Some(artifacts) = &state.config.available_artifacts {
let filtered = artifacts
.iter()
.filter(|(id, _)| {
// name might be in the form of "Counter.0.8.23"
let id_name = id.name.split('.').next().unwrap();

if let Some(path) = &file {
if !id.source.ends_with(path) {
return false;
}
}
if let Some(name) = contract_name {
if id_name != name {
return false;
}
}
if let Some(ref version) = version {
if id.version.minor != version.minor
|| id.version.major != version.major
|| id.version.patch != version.patch
{
return false;
}
}
true
})
.collect::<Vec<_>>();

let artifact = match filtered.len() {
0 => Err(fmt_err!("No matching artifact found")),
1 => Ok(filtered[0]),
_ => {
// If we know the current script/test contract solc version, try to filter by it
state
.config
.running_version
.as_ref()
.and_then(|version| {
let filtered = filtered
.into_iter()
.filter(|(id, _)| id.version == *version)
.collect::<Vec<_>>();

(filtered.len() == 1).then_some(filtered[0])
})
.ok_or_else(|| fmt_err!("Multiple matching artifacts found"))
}
}?;

let maybe_bytecode = if deployed {
artifact.1.deployed_bytecode.clone()
} else {
artifact.1.bytecode.clone()
};
let version = if let Some(version) = version {
Some(Version::parse(version).map_err(|_err| fmt_err!("Error parsing version"))?)
} else {
None
};

return maybe_bytecode
.ok_or_else(|| fmt_err!("No bytecode for contract. Is it abstract or unlinked?"));
} else {
match (file.map(|f| f.to_string_lossy().to_string()), contract_name) {
(Some(file), Some(contract_name)) => {
PathBuf::from(format!("{file}/{contract_name}.json"))
let filtered = state
.config
.available_artifacts
.iter()
.filter(|(id, _)| {
// name might be in the form of "Counter.0.8.23"
let id_name = id.name.split('.').next().unwrap();

if let Some(path) = &file {
if !id.source.ends_with(path) {
return false;
}
(None, Some(contract_name)) => {
PathBuf::from(format!("{contract_name}.sol/{contract_name}.json"))
}
if let Some(name) = contract_name {
if id_name != name {
return false;
}
(Some(file), None) => {
let name = file.replace(".sol", "");
PathBuf::from(format!("{file}/{name}.json"))
}
if let Some(ref version) = version {
if id.version.minor != version.minor
|| id.version.major != version.major
|| id.version.patch != version.patch
{
return false;
}
_ => return Err(fmt_err!("Invalid artifact path")),
}
true
})
.collect::<Vec<_>>();

let artifact = match filtered.len() {
0 => Err(fmt_err!("No matching artifact found")),
1 => Ok(filtered[0]),
_ => {
// If we know the current script/test contract solc version, try to filter by it
state
.config
.running_version
.as_ref()
.and_then(|version| {
let filtered = filtered
.into_iter()
.filter(|(id, _)| id.version == *version)
.collect::<Vec<_>>();

(filtered.len() == 1).then_some(filtered[0])
})
.ok_or_else(|| fmt_err!("Multiple matching artifacts found"))
}
};
}?;

let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
let data = fs::read_to_string(path)?;
let artifact = serde_json::from_str::<ContractObject>(&data)?;
let maybe_bytecode = if deployed {
artifact.deployed_bytecode
artifact.1.deployed_bytecode.clone()
} else {
artifact.bytecode
artifact.1.bytecode.clone()
};

maybe_bytecode.ok_or_else(|| fmt_err!("No bytecode for contract. Is it abstract or unlinked?"))
}

Expand Down Expand Up @@ -606,6 +575,8 @@ fn prompt(
mod tests {
use std::sync::Arc;

use alloy_sol_types::private::alloy_json_abi::ContractObject;

use super::*;
use crate::CheatsConfig;

Expand Down
2 changes: 1 addition & 1 deletion crates/foundry/forge/src/multi_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ impl MultiContractRunner {
self.project_root.clone(),
(*self.cheats_config_options).clone(),
self.evm_opts.clone(),
Some(self.known_contracts.clone()),
self.known_contracts.clone(),
Some(artifact_id.version.clone()),
);

Expand Down
2 changes: 1 addition & 1 deletion crates/foundry/testdata/default/cheats/Fs.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ contract FsTest is DSTest {
}

function testCreateRemoveDir() public {
string memory path = "fixtures/Dir/remove_dir";
string memory path = "fixtures/CreateRemoveDir/remove_dir";
string memory child = string.concat(path, "/child");

vm.createDir(path, false);
Expand Down
Loading

0 comments on commit 3ba3eb4

Please sign in to comment.