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

test: add forge create test with lib linking #1441

Merged
merged 6 commits into from
Apr 28, 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
22 changes: 11 additions & 11 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion cli/src/cmd/forge/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl Cmd for CreateArgs {
// Find Project & Compile
let project = self.opts.project()?;
let compiled = if self.json {
// Supress compile stdout messages when printing json output
// Suppress compile stdout messages when printing json output
compile::suppress_compile(&project)?
} else {
compile::compile(&project, false, false)?
Expand Down
72 changes: 72 additions & 0 deletions cli/tests/it/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Contains various tests for checking the `forge create` subcommand

use crate::utils::{self, EnvExternalities};
use ethers::{solc::remappings::Remapping, types::Address};
use foundry_cli_test_utils::{
forgetest,
util::{TestCommand, TestProject},
};
use foundry_config::Config;
use std::str::FromStr;

/// This will insert _dummy_ contract that uses a library
///
/// **NOTE** This is intended to be linked against a random address and won't actually work. The
/// purpose of this is _only_ to make sure we can deploy contracts linked against addresses.
///
/// This will create a library `remapping/MyLib.sol:MyLib`
fn setup_with_remapping(prj: &mut TestProject) {
// explicitly set remapping and libraries
let config = Config {
remappings: vec![Remapping::from_str("remapping/=lib/remapping/").unwrap().into()],
libraries: vec![format!("remapping/MyLib.sol:MyLib:{:?}", Address::random())],
..Default::default()
};
prj.write_config(config);

prj.inner()
.add_source(
"LinkTest",
r#"
// SPDX-License-Identifier: MIT
import "remapping/MyLib.sol";
contract LinkTest {
function foo() public returns (uint256) {
return MyLib.foobar(1);
}
}
"#,
)
.unwrap();

prj.inner()
.add_lib(
"remapping/MyLib",
r#"
// SPDX-License-Identifier: MIT
library MyLib {
function foobar(uint256 a) public view returns (uint256) {
return a * 100;
}
}
"#,
)
.unwrap();
}

fn create_on_chain(info: Option<EnvExternalities>, mut prj: TestProject, mut cmd: TestCommand) {
if let Some(info) = info {
setup_with_remapping(&mut prj);
cmd.arg("create");
cmd.args(info.create_args()).arg("src/LinkTest.sol:LinkTest");

let out = cmd.stdout_lossy();
let _address = utils::parse_deployed_address(out.as_str())
.unwrap_or_else(|| panic!("Failed to parse deployer {out}"));
}
}

// tests `forge` create on goerli if correct env vars are set
forgetest!(can_create_on_goerli, |prj: TestProject, cmd: TestCommand| {
create_on_chain(EnvExternalities::goerli(), prj, cmd);
});
4 changes: 4 additions & 0 deletions cli/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ mod cmd;
#[cfg(not(feature = "external-integration-tests"))]
mod config;
#[cfg(not(feature = "external-integration-tests"))]
mod create;
#[cfg(not(feature = "external-integration-tests"))]
mod test_cmd;
#[cfg(not(feature = "external-integration-tests"))]
mod utils;
#[cfg(not(feature = "external-integration-tests"))]
mod verify;

#[cfg(feature = "external-integration-tests")]
Expand Down
91 changes: 91 additions & 0 deletions cli/tests/it/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! Various helper functions

use ethers::prelude::Chain;

/// Returns the current millis since unix epoch.
///
/// This way we generate unique contracts so, etherscan will always have to verify them
pub fn millis_since_epoch() -> u128 {
let now = std::time::SystemTime::now();
now.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap_or_else(|err| panic!("Current time {:?} is invalid: {:?}", now, err))
.as_millis()
}

pub fn etherscan_key(chain: Chain) -> Option<String> {
match chain {
Chain::Fantom | Chain::FantomTestnet => {
std::env::var("FTMSCAN_API_KEY").or_else(|_| std::env::var("FANTOMSCAN_API_KEY")).ok()
}
_ => std::env::var("ETHERSCAN_API_KEY").ok(),
}
}

pub fn network_rpc_key(chain: &str) -> Option<String> {
let key = format!("{}_RPC_URL", chain.to_uppercase());
std::env::var(&key).ok()
}

pub fn network_private_key(chain: &str) -> Option<String> {
let key = format!("{}_PRIVATE_KEY", chain.to_uppercase());
std::env::var(&key).ok()
}

/// Represents external input required for executing verification requests
pub struct EnvExternalities {
pub chain: Chain,
pub rpc: String,
pub pk: String,
pub etherscan: String,
}

impl EnvExternalities {
pub fn goerli() -> Option<Self> {
Some(Self {
chain: Chain::Goerli,
rpc: network_rpc_key("goerli")?,
pk: network_private_key("goerli")?,
etherscan: etherscan_key(Chain::Goerli)?,
})
}

pub fn ftm_testnet() -> Option<Self> {
Some(Self {
chain: Chain::FantomTestnet,
rpc: network_rpc_key("ftm_testnet")?,
pk: network_private_key("ftm_testnet")?,
etherscan: etherscan_key(Chain::FantomTestnet)?,
})
}

/// Returns the arguments required to deploy the contract
pub fn create_args(&self) -> Vec<String> {
vec![
"--chain".to_string(),
self.chain.to_string(),
"--rpc-url".to_string(),
self.rpc.clone(),
"--private-key".to_string(),
self.pk.clone(),
]
}
}

/// Parses the address the contract was deployed to
pub fn parse_deployed_address(out: &str) -> Option<String> {
for line in out.lines() {
if line.starts_with("Deployed to") {
return Some(line.trim_start_matches("Deployed to: ").to_string())
}
}
None
}

pub fn parse_verification_guid(out: &str) -> Option<String> {
for line in out.lines() {
if line.contains("GUID") {
return Some(line.replace("GUID:", "").replace('`', "").trim().to_string())
}
}
None
}
Loading