diff --git a/Cargo.lock b/Cargo.lock index 386b29cc9995..1414d38ef6d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,9 +65,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "arrayref" @@ -161,9 +161,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b" +checksum = "6f3132262930b0522068049f5870a856ab8affc80c70d08b6ecb785771a6fc23" dependencies = [ "serde", ] @@ -532,9 +532,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.10" +version = "3.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375" +checksum = "08799f92c961c7a1cf0cc398a9073da99e21ce388b46372c37f3191f2f3eed3e" dependencies = [ "atty", "bitflags", @@ -551,18 +551,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d044e9db8cd0f68191becdeb5246b7462e4cf0c069b19ae00d1bf3fa9889498d" +checksum = "be4dabb7e2f006497e1da045feaa512acf0686f76b68d94925da2d9422dcb521" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "3.0.6" +version = "3.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153" +checksum = "0fd2078197a22f338bd4fbf7d6387eb6f0d6a3c69e6cbc09f5c93e97321fd92a" dependencies = [ "heck 0.4.0", "proc-macro-error 1.0.4", @@ -1589,9 +1589,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" +checksum = "bc225d8f637923fe585089fcf03e705c222131232d2c1fb622e84ecf725d0eb8" dependencies = [ "indenter", "once_cell", @@ -1605,9 +1605,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fastrand" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -1692,6 +1692,7 @@ dependencies = [ "proptest", "rayon", "regex", + "rlp", "semver", "serde", "serde_json", @@ -1785,9 +1786,12 @@ dependencies = [ "ethers-addressbook", "ethers-core", "ethers-etherscan", + "ethers-providers", + "ethers-solc", "eyre", "hex", "reqwest", + "rlp", "rustc-hex", "serde", "serde_json", @@ -1947,9 +1951,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2008,9 +2012,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ "bytes", "fnv", @@ -2347,9 +2351,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -2376,9 +2380,9 @@ checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "lalrpop" -version = "0.19.6" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" +checksum = "852b75a095da6b69da8c5557731c3afd06525d4f655a4fc1c799e2ec8bc4dce4" dependencies = [ "ascii-canvas", "atty", @@ -2399,9 +2403,9 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.19.6" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" +checksum = "d6d265705249fe209280676d8f68887859fa42e1d34f342fc05bd47726a5e188" dependencies = [ "regex", ] @@ -2417,9 +2421,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.112" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" [[package]] name = "libgit2-sys" @@ -2459,9 +2463,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -2744,9 +2748,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" @@ -3383,7 +3387,7 @@ dependencies = [ [[package]] name = "revm_precompiles" version = "0.4.0" -source = "git+https://github.com/bluealloy/revm#4f5bfe40a1607130e5b6f8460866653642191f5a" +source = "git+https://github.com/bluealloy/revm#1a5b05d033bddb0e540d505846fe0df61f8484bb" dependencies = [ "borsh", "bytes", @@ -3647,9 +3651,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "d09d3c15d814eda1d6a836f2f2b56a6abc1446c8a34351cb3180d3db92ffe4ce" dependencies = [ "bitflags", "core-foundation", @@ -3660,9 +3664,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "e90dd10c41c6bfc633da6e0c659bd25d31e0791e5974ac42970267d59eba87f7" dependencies = [ "core-foundation-sys", "libc", @@ -3685,9 +3689,9 @@ checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7" [[package]] name = "serde" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] @@ -3704,9 +3708,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -3715,9 +3719,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.74" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ "itoa 1.0.1", "ryu", @@ -3726,12 +3730,12 @@ dependencies = [ [[package]] name = "serde_urlencoded" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 0.4.8", + "itoa 1.0.1", "ryu", "serde", ] @@ -3881,9 +3885,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" [[package]] name = "slab" @@ -3893,15 +3897,15 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", @@ -4143,9 +4147,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ "once_cell", ] @@ -4186,9 +4190,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ "bytes", "libc", @@ -4463,9 +4467,9 @@ dependencies = [ [[package]] name = "uint" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +checksum = "1b1b413ebfe8c2c74a69ff124699dd156a7fa41cb1d09ba6df94aa2f2b0a4a3a" dependencies = [ "byteorder", "crunchy", @@ -4566,9 +4570,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "6.0.0" +version = "6.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0c9f8387e118573859ae0e6c6fbdfa41bd1f4fbea451b0b8c5a81a3b8bc9e0" +checksum = "3893329bee75c101278e0234b646fa72221547d63f97fb66ac112a0569acd110" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -4631,9 +4635,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -4641,9 +4645,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -4656,9 +4660,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -4668,9 +4672,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4678,9 +4682,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -4691,9 +4695,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "wasm-timer" @@ -4712,9 +4716,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" dependencies = [ "js-sys", "wasm-bindgen", @@ -4811,6 +4815,6 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" [[package]] name = "zeroize" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc222aec311c323c717f56060324f32b82da1ce1dd81d9a09aa6a9030bfe08db" +checksum = "4062c749be08d90be727e9c5895371c3a0e49b90ba2b9592dc7afda95cc2b719" diff --git a/Cargo.toml b/Cargo.toml index ebfe920da9a0..d77993a938b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,4 @@ debug = true #ethers-providers = { path = "../ethers-rs/ethers-providers" } #ethers-signers = { path = "../ethers-rs/ethers-signers" } #ethers-etherscan = { path = "../ethers-rs/ethers-etherscan" } -#ethers-solc = { path = "../ethers-rs/ethers-solc" } \ No newline at end of file +#ethers-solc = { path = "../ethers-rs/ethers-solc" } \ No newline at end of file diff --git a/cli/src/cmd/run.rs b/cli/src/cmd/run.rs index 1204d3c1fe01..65ac0ccf4e2c 100644 --- a/cli/src/cmd/run.rs +++ b/cli/src/cmd/run.rs @@ -1,6 +1,7 @@ use crate::cmd::{build::BuildArgs, compile, manual_compile, Cmd}; use clap::{Parser, ValueHint}; -use ethers::{abi::Abi, prelude::artifacts::CompactContract}; +use evm_adapters::sputnik::cheatcodes::{CONSOLE_ABI, HEVMCONSOLE_ABI, HEVM_ABI}; + use forge::ContractRunner; use foundry_utils::IntoFunction; use std::{collections::BTreeMap, path::PathBuf}; @@ -11,8 +12,8 @@ use ethers::solc::{MinimalCombinedArtifacts, Project}; use crate::opts::evm::EvmArgs; use ansi_term::Colour; use ethers::{ - prelude::artifacts::ContractBytecode, - solc::artifacts::{CompactContractSome, ContractBytecodeSome}, + abi::Abi, + solc::artifacts::{CompactContractBytecode, ContractBytecode, ContractBytecodeSome}, }; use evm_adapters::{ call_tracing::ExecutionInfo, @@ -20,6 +21,7 @@ use evm_adapters::{ sputnik::{cheatcodes::debugger::DebugArena, helpers::vm}, }; use foundry_config::{figment::Figment, Config}; +use foundry_utils::PostLinkInput; // Loads project's figment and merges the build cli arguments into it foundry_config::impl_figment_convert!(RunArgs, opts, evm_opts); @@ -66,10 +68,15 @@ impl Cmd for RunArgs { } let func = IntoFunction::into(self.sig.as_deref().unwrap_or("run()")); - let BuildOutput { project, contract, highlevel_known_contracts, sources } = - self.build(config)?; + let BuildOutput { + project, + contract, + highlevel_known_contracts, + sources, + predeploy_libraries, + } = self.build(config, &evm_opts)?; - let known_contracts = highlevel_known_contracts + let mut known_contracts = highlevel_known_contracts .iter() .map(|(name, c)| { ( @@ -82,9 +89,13 @@ impl Cmd for RunArgs { }) .collect::)>>(); - let CompactContractSome { abi, bin, .. } = contract; - // this should never fail if compilation was successful - let bytecode = bin.into_bytes().unwrap(); + known_contracts.insert("VM".to_string(), (HEVM_ABI.clone(), Vec::new())); + known_contracts.insert("VM_CONSOLE".to_string(), (HEVMCONSOLE_ABI.clone(), Vec::new())); + known_contracts.insert("CONSOLE".to_string(), (CONSOLE_ABI.clone(), Vec::new())); + + let CompactContractBytecode { abi, bytecode, .. } = contract; + let abi = abi.expect("No abi for contract"); + let bytecode = bytecode.expect("No bytecode").object.into_bytes().unwrap(); let needs_setup = abi.functions().any(|func| func.name == "setUp"); let mut cfg = crate::utils::sputnik_cfg(&evm_version); @@ -103,6 +114,7 @@ impl Cmd for RunArgs { bytecode, Some(evm_opts.sender), None, + &predeploy_libraries, ); runner.run_test(&func, needs_setup, Some(&known_contracts))? } @@ -115,6 +127,7 @@ impl Cmd for RunArgs { bytecode, Some(evm_opts.sender), None, + &predeploy_libraries, ); runner.run_test(&func, needs_setup, Some(&known_contracts))? } @@ -205,8 +218,25 @@ impl Cmd for RunArgs { if !trace_string.is_empty() { println!("{}", trace_string); } + } else { + // 5. print the result nicely + if result.success { + println!("{}", Colour::Green.paint("Script ran successfully.")); + } else { + println!("{}", Colour::Red.paint("Script failed.")); + } + + println!("Gas Used: {}", result.gas_used); + println!("== Logs == "); + result.logs.iter().for_each(|log| println!("{}", log)); } println!(); + } else if result.traces.is_none() { + eyre::bail!("Unexpected error: No traces despite verbosity level. Please report this as a bug: https://github.com/gakonst/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml"); + } else if result.identified_contracts.is_none() { + eyre::bail!( + "Unexpected error: No identified contracts. Please report this as a bug: https://github.com/gakonst/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml" + ); } } else { // 5. print the result nicely @@ -225,16 +255,25 @@ impl Cmd for RunArgs { } } +struct ExtraLinkingInfo<'a> { + no_target_name: bool, + target_fname: String, + contract: &'a mut CompactContractBytecode, + dependencies: &'a mut Vec, + matched: bool, +} + pub struct BuildOutput { pub project: Project, - pub contract: CompactContractSome, + pub contract: CompactContractBytecode, pub highlevel_known_contracts: BTreeMap, pub sources: BTreeMap, + pub predeploy_libraries: Vec, } impl RunArgs { /// Compiles the file with auto-detection and compiler params. - pub fn build(&self, config: Config) -> eyre::Result { + pub fn build(&self, config: Config, evm_opts: &EvmOpts) -> eyre::Result { let target_contract = dunce::canonicalize(&self.path)?; let (project, output) = if let Ok(mut project) = config.project() { // TODO: caching causes no output until https://github.com/gakonst/ethers-rs/issues/727 @@ -243,7 +282,7 @@ impl RunArgs { project.no_artifacts = true; // target contract may not be in the compilation path, add it and manually compile - match manual_compile(&project, vec![target_contract.clone()]) { + match manual_compile(&project, vec![target_contract]) { Ok(output) => (project, output), Err(e) => { println!("No extra contracts compiled {:?}", e); @@ -263,69 +302,85 @@ impl RunArgs { }; println!("success."); - // get the contracts - let (sources, contracts) = output.output().split(); - - // get the specific contract - let contract_bytecode = if let Some(contract_name) = self.target_contract.clone() { - let contract_bytecode: ContractBytecode = contracts - .0 - .get(target_contract.to_str().expect("OsString from path")) - .ok_or_else(|| { - eyre::Error::msg( - "contract path not found; This is likely a bug, please report it", - ) - })? - .get(&contract_name) - .ok_or_else(|| { - eyre::Error::msg("contract not found, did you type the name wrong?") - })? - .clone() - .into(); - contract_bytecode.unwrap() - } else { - let contract = contracts - .0 - .get(target_contract.to_str().expect("OsString from path")) - .ok_or_else(|| { - eyre::Error::msg( - "contract path not found; This is likely a bug, please report it", - ) - })? - .clone() - .into_iter() - .filter_map(|(name, c)| { - let c: ContractBytecode = c.into(); - ContractBytecodeSome::try_from(c).ok().map(|c| (name, c)) - }) - .find(|(_, c)| c.bytecode.object.is_non_empty_bytecode()) - .ok_or_else(|| eyre::Error::msg("no contract found"))?; - contract.1 - }; + let (sources, all_contracts) = output.output().split(); - let contract = CompactContract::from(contract_bytecode).try_into().expect("Couldn't create contract from bytecodes, either abi, bytecode, or deployed_bytecode were empty."); + let mut contracts: BTreeMap = BTreeMap::new(); + all_contracts.0.iter().for_each(|(source, output_contracts)| { + contracts.extend( + output_contracts + .iter() + .map(|(n, c)| (format!("{}:{}", source, n), c.clone().into())) + .collect::>(), + ); + }); + let mut run_dependencies = vec![]; + let mut contract = + CompactContractBytecode { abi: None, bytecode: None, deployed_bytecode: None }; let mut highlevel_known_contracts = BTreeMap::new(); - // build the entire highlevel_known_contracts based on all compiled contracts - contracts.0.into_iter().for_each(|(src, mapping)| { - mapping.into_iter().for_each(|(name, c)| { - let cb: ContractBytecode = c.into(); - if let Ok(cbs) = ContractBytecodeSome::try_from(cb) { - if highlevel_known_contracts.contains_key(&name) { - highlevel_known_contracts.insert(src.to_string() + ":" + &name, cbs); - } else { - highlevel_known_contracts.insert(name, cbs); + let mut target_fname = dunce::canonicalize(&self.path) + .expect("Couldn't convert contract path to absolute path") + .to_str() + .expect("Bad path to string") + .to_string(); + + let no_target_name = if let Some(target_name) = &self.target_contract { + target_fname = target_fname + ":" + target_name; + false + } else { + true + }; + + foundry_utils::link( + &contracts, + &mut highlevel_known_contracts, + evm_opts.sender, + &mut ExtraLinkingInfo { + no_target_name, + target_fname, + contract: &mut contract, + dependencies: &mut run_dependencies, + matched: false, + }, + |file, key| (format!("{}:{}", file, key), file, key), + |post_link_input| { + let PostLinkInput { + contract, + known_contracts: highlevel_known_contracts, + fname, + extra, + dependencies, + } = post_link_input; + let split = fname.split(':').collect::>(); + + // if its the target contract, grab the info + if extra.no_target_name && split[0] == extra.target_fname { + if extra.matched { + eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `-t ContractName`") } + *extra.dependencies = dependencies; + *extra.contract = contract.clone(); + extra.matched = true; + } else if extra.target_fname == fname { + *extra.dependencies = dependencies; + *extra.contract = contract.clone(); + extra.matched = true; } - }); - }); + + let tc: ContractBytecode = contract.into(); + let contract_name = if split.len() > 1 { split[1] } else { split[0] }; + highlevel_known_contracts.insert(contract_name.to_string(), tc.unwrap()); + Ok(()) + }, + )?; Ok(BuildOutput { project, contract, highlevel_known_contracts, sources: sources.into_ids().collect(), + predeploy_libraries: run_dependencies, }) } } diff --git a/cli/testdata/run_test_lib_linking.sol b/cli/testdata/run_test_lib_linking.sol new file mode 100644 index 000000000000..eb42dfc73a7e --- /dev/null +++ b/cli/testdata/run_test_lib_linking.sol @@ -0,0 +1,38 @@ +pragma solidity ^0.7.6; + +interface ERC20 { + function balanceOf(address) external view returns (uint256); + function deposit() payable external; +} + +interface VM { + function startPrank(address) external; +} + +library T { + function getBal(ERC20 t, address who) public view returns (uint256) { + return t.balanceOf(who); + } +} + +contract C { + ERC20 weth = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + VM constant vm = VM(address(bytes20(uint160(uint256(keccak256('hevm cheat code')))))); + address who = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; + + event log_uint(uint256); + + function run() external { + // impersonate the account + vm.startPrank(who); + + uint256 balanceBefore = T.getBal(weth, who); + emit log_uint(balanceBefore); + + weth.deposit{value: 15 ether}(); + + uint256 balanceAfter = weth.balanceOf(who); + emit log_uint(balanceAfter); + + } +} \ No newline at end of file diff --git a/evm-adapters/src/blocking_provider.rs b/evm-adapters/src/blocking_provider.rs index 2558d07c1ee8..e2a488bcf513 100644 --- a/evm-adapters/src/blocking_provider.rs +++ b/evm-adapters/src/blocking_provider.rs @@ -3,23 +3,7 @@ use ethers::{ providers::Middleware, types::{Address, Block, BlockId, Bytes, TxHash, H256, U256, U64}, }; -use tokio::runtime::{Handle, Runtime}; - -#[allow(clippy::large_enum_variant)] -#[derive(Debug)] -pub enum RuntimeOrHandle { - Runtime(Runtime), - Handle(Handle), -} - -impl RuntimeOrHandle { - pub fn new() -> RuntimeOrHandle { - match Handle::try_current() { - Ok(handle) => RuntimeOrHandle::Handle(handle), - Err(_) => RuntimeOrHandle::Runtime(Runtime::new().expect("Failed to start runtime")), - } - } -} +use foundry_utils::RuntimeOrHandle; #[derive(Debug)] /// Blocking wrapper around an Ethers middleware, for use in synchronous contexts @@ -46,10 +30,7 @@ where /// Receives a future and runs it to completion. fn block_on(&self, f: F) -> F::Output { - match &self.runtime { - RuntimeOrHandle::Runtime(runtime) => runtime.block_on(f), - RuntimeOrHandle::Handle(handle) => tokio::task::block_in_place(|| handle.block_on(f)), - } + self.runtime.block_on(f) } /// Gets the specified block as well as the chain id concurrently. diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index f4cb38cfb25a..f07a7b5e38e5 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -546,9 +546,11 @@ impl CallTrace { )); if !self.output.is_empty() && self.success { - return Output::Token( - func.decode_output(&self.output[..]).expect("Bad func output decode"), - ) + if let Ok(tokens) = func.decode_output(&self.output[..]) { + return Output::Token(tokens) + } else { + return Output::Raw(self.output[..].to_vec()) + } } else if !self.output.is_empty() && !self.success { if let Ok(decoded_error) = foundry_utils::decode_revert(&self.output[..], Some(exec_info.errors)) diff --git a/evm-adapters/src/sputnik/forked_backend/cache.rs b/evm-adapters/src/sputnik/forked_backend/cache.rs index 7a7319ecdb80..6e353bb776c1 100644 --- a/evm-adapters/src/sputnik/forked_backend/cache.rs +++ b/evm-adapters/src/sputnik/forked_backend/cache.rs @@ -21,7 +21,7 @@ use std::{ }, }; -use crate::blocking_provider::RuntimeOrHandle; +use foundry_utils::RuntimeOrHandle; /// A basic in memory cache (address -> Account) pub type MemCache = BTreeMap; diff --git a/forge/Cargo.toml b/forge/Cargo.toml index b14337dec8a7..4ffc26ef65c3 100644 --- a/forge/Cargo.toml +++ b/forge/Cargo.toml @@ -25,6 +25,7 @@ tracing = "0.1.26" tracing-subscriber = "0.2.20" proptest = "1.0.0" rayon = "1.5" +rlp = "0.5.1" # load sputnik for parallel evm usage sputnik = { package = "evm", git = "https://github.com/rust-blockchain/evm" } diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index bc503f368a5c..9835794d0187 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -1,8 +1,10 @@ use crate::{runner::TestResult, ContractRunner, TestFilter}; +use ethers::prelude::artifacts::CompactContractBytecode; use evm_adapters::{ evm_opts::{BackendKind, EvmOpts}, sputnik::cheatcodes::{CONSOLE_ABI, HEVMCONSOLE_ABI, HEVM_ABI}, }; +use foundry_utils::PostLinkInput; use sputnik::{backend::Backend, Config}; use ethers::solc::Artifact; @@ -34,6 +36,9 @@ pub struct MultiContractRunnerBuilder { pub evm_cfg: Option, } +pub type DeployableContracts = + BTreeMap)>; + impl MultiContractRunnerBuilder { /// Given an EVM, proceeds to return a runner which is able to execute all tests /// against that evm @@ -55,32 +60,59 @@ impl MultiContractRunnerBuilder { // This is just the contracts compiled, but we need to merge this with the read cached // artifacts - let contracts = output.into_artifacts(); + let contracts = output + .into_artifacts() + .map(|(n, c)| (n, c.into_contract_bytecode())) + .collect::>(); let mut known_contracts: BTreeMap)> = Default::default(); - let mut deployable_contracts: BTreeMap = - Default::default(); - - for (fname, contract) in contracts { - let (maybe_abi, maybe_deploy_bytes, maybe_runtime_bytes) = contract.into_parts(); - if let (Some(abi), Some(bytecode)) = (maybe_abi, maybe_deploy_bytes) { - // skip deployment of abstract contracts - if bytecode.as_ref().is_empty() { - continue - } + // create a mapping of name => (abi, deployment code, Vec) + let mut deployable_contracts = DeployableContracts::default(); + + foundry_utils::link( + &contracts, + &mut known_contracts, + evm_opts.sender, + &mut deployable_contracts, + |file, key| (format!("{}.json:{}", key, key), file, key), + |post_link_input| { + let PostLinkInput { + contract, + known_contracts, + fname, + extra: deployable_contracts, + dependencies, + } = post_link_input; + + // get bytes + let bytecode = + if let Some(b) = contract.bytecode.expect("No bytecode").object.into_bytes() { + b + } else { + return Ok(()) + }; + + let abi = contract.abi.expect("We should have an abi by now"); + // if its a test, add it to deployable contracts if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && abi.functions().any(|func| func.name.starts_with("test")) { - deployable_contracts.insert(fname.clone(), (abi.clone(), bytecode.clone())); + deployable_contracts + .insert(fname.clone(), (abi.clone(), bytecode, dependencies.to_vec())); } let split = fname.split(':').collect::>(); let contract_name = if split.len() > 1 { split[1] } else { split[0] }; - if let Some(runtime_code) = maybe_runtime_bytes { - known_contracts.insert(contract_name.to_string(), (abi, runtime_code.to_vec())); - } - } - } + contract + .deployed_bytecode + .and_then(|d_bcode| d_bcode.bytecode) + .and_then(|bcode| bcode.object.into_bytes()) + .and_then(|bytes| { + known_contracts.insert(contract_name.to_string(), (abi, bytes.to_vec())) + }); + Ok(()) + }, + )?; // add forge+sputnik specific contracts known_contracts.insert("VM".to_string(), (HEVM_ABI.clone(), Vec::new())); @@ -128,8 +160,9 @@ impl MultiContractRunnerBuilder { /// A multi contract runner receives a set of contracts deployed in an EVM instance and proceeds /// to run all test functions in these contracts. pub struct MultiContractRunner { - /// Mapping of contract name to Abi and creation bytecode - pub contracts: BTreeMap, + /// Mapping of contract name to Abi, creation bytecode and library bytecode which + /// needs to be deployed & linked against + pub contracts: BTreeMap)>, /// Compiled contracts by name that have an Abi and runtime bytecode pub known_contracts: BTreeMap)>, /// Identified contracts by test @@ -160,14 +193,14 @@ impl MultiContractRunner { let results = contracts .par_iter() .filter(|(name, _)| filter.matches_contract(name)) - .map(|(name, (abi, deploy_code))| { + .map(|(name, (abi, deploy_code, libs))| { // unavoidable duplication here? let result = match backend { BackendKind::Simple(ref backend) => { - self.run_tests(name, abi, backend, deploy_code.clone(), filter)? + self.run_tests(name, abi, backend, deploy_code.clone(), libs, filter)? } BackendKind::Shared(ref backend) => { - self.run_tests(name, abi, backend, deploy_code.clone(), filter)? + self.run_tests(name, abi, backend, deploy_code.clone(), libs, filter)? } }; Ok((name.clone(), result)) @@ -194,6 +227,7 @@ impl MultiContractRunner { contract: &Abi, backend: &B, deploy_code: ethers::prelude::Bytes, + libs: &[ethers::prelude::Bytes], filter: &impl TestFilter, ) -> Result> { let runner = ContractRunner::new( @@ -204,6 +238,7 @@ impl MultiContractRunner { deploy_code, self.sender, Some((&self.execution_info.0, &self.execution_info.1, &self.execution_info.2)), + libs, ); runner.run_tests(filter, self.fuzzer.clone(), Some(&self.known_contracts)) } @@ -240,8 +275,8 @@ mod tests { let mut runner = runner(); let results = runner.test(&Filter::new(".*", ".*")).unwrap(); - // 8 contracts being built - assert_eq!(results.keys().len(), 8); + // 9 contracts being built + assert_eq!(results.keys().len(), 9); for (key, contract_tests) in results { // for a bad setup, we dont want a successful test if key == "SetupTest.json:SetupTest" { diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 79c34e85e486..d52e35b73f1b 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -167,9 +167,12 @@ pub struct ContractRunner<'a, B> { /// Contract execution info, (functions, events, errors) pub execution_info: MaybeExecutionInfo<'a>, + /// library contracts to be deployed before this contract + pub predeploy_libs: &'a [ethers::prelude::Bytes], } impl<'a, B: Backend> ContractRunner<'a, B> { + #[allow(clippy::too_many_arguments)] pub fn new( evm_opts: &'a EvmOpts, evm_cfg: &'a Config, @@ -178,6 +181,7 @@ impl<'a, B: Backend> ContractRunner<'a, B> { code: ethers::prelude::Bytes, sender: Option
, execution_info: MaybeExecutionInfo<'a>, + predeploy_libs: &'a [ethers::prelude::Bytes], ) -> Self { Self { evm_opts, @@ -187,6 +191,7 @@ impl<'a, B: Backend> ContractRunner<'a, B> { code, sender: sender.unwrap_or_default(), execution_info, + predeploy_libs, } } } @@ -208,6 +213,12 @@ impl<'a, B: Backend + Clone + Send + Sync> ContractRunner<'a, B> { self.evm_opts.debug, ); + self.predeploy_libs.iter().for_each(|code| { + executor + .deploy(self.sender, code.clone(), 0u32.into()) + .expect("couldn't deploy library"); + }); + // deploy an instance of the contract inside the runner in the EVM let (addr, _, _, logs) = executor.deploy(self.sender, self.code.clone(), 0u32.into()).expect("couldn't deploy"); @@ -592,8 +603,9 @@ mod tests { pub fn runner<'a>( abi: &'a Abi, code: ethers::prelude::Bytes, + libs: &'a mut Vec, ) -> ContractRunner<'a, MemoryBackend<'a>> { - ContractRunner::new(&*EVM_OPTS, &*CFG_NO_LMT, &*BACKEND, abi, code, None, None) + ContractRunner::new(&*EVM_OPTS, &*CFG_NO_LMT, &*BACKEND, abi, code, None, None, libs) } #[test] @@ -607,7 +619,8 @@ mod tests { let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); let (_, code, _) = compiled.into_parts_or_default(); - let runner = runner(compiled.abi.as_ref().unwrap(), code); + let mut libs = vec![]; + let runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; @@ -623,7 +636,8 @@ mod tests { fn test_fuzzing_counterexamples() { let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); let (_, code, _) = compiled.into_parts_or_default(); - let runner = runner(compiled.abi.as_ref().unwrap(), code); + let mut libs = vec![]; + let runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; @@ -640,7 +654,8 @@ mod tests { fn test_fuzzing_ok() { let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); let (_, code, _) = compiled.into_parts_or_default(); - let runner = runner(compiled.abi.as_ref().unwrap(), code); + let mut libs = vec![]; + let runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; @@ -655,7 +670,8 @@ mod tests { fn test_fuzz_shrinking() { let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); let (_, code, _) = compiled.into_parts_or_default(); - let runner = runner(compiled.abi.as_ref().unwrap(), code); + let mut libs = vec![]; + let runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; @@ -691,7 +707,8 @@ mod tests { pub fn test_runner(compiled: CompactContractRef) { let (_, code, _) = compiled.into_parts_or_default(); - let runner = sputnik::runner(compiled.abi.as_ref().unwrap(), code); + let mut libs = vec![]; + let runner = sputnik::runner(compiled.abi.as_ref().unwrap(), code, &mut libs); let res = runner.run_tests(&Filter::new(".*", ".*"), None, None).unwrap(); assert!(!res.is_empty()); diff --git a/forge/testdata/LibLinking.sol b/forge/testdata/LibLinking.sol new file mode 100644 index 000000000000..53a59b1921fa --- /dev/null +++ b/forge/testdata/LibLinking.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.11; + +// a library that needs to be linked with another library +library LibTestNested { + enum TestEnum2 { + A, + B, + C + } + + function foobar(TestEnum2 test) public view returns (uint256) { + return LibTest.foobar(101); + } +} + +// a library +library LibTest { + function foobar(uint256 a) public view returns (uint256) { + return a * 100; + } +} + + +// a contract that uses 2 linked libraries +contract Main { + function foo() public returns (uint256) { + return LibTest.foobar(1); + } + + function bar() public returns (uint256) { + return LibTestNested.foobar(LibTestNested.TestEnum2(0)); + } +} + +contract DsTestMini { + bool public failed; + + function fail() private { + failed = true; + } + + function assertEq(uint a, uint b) internal { + if (a != b) { + fail(); + } + } +} + + +contract LibLinkingTest is DsTestMini { + Main main; + function setUp() public { + main = new Main(); + } + + function testCall() public { + assertEq(100, main.foo()); + } + + function testCall2() public { + assertEq(10100, main.bar()); + } +} diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 4462efed6c16..adb231ee5725 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -11,6 +11,9 @@ license = "MIT OR Apache-2.0" ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } ethers-etherscan = { git = "https://github.com/gakonst/ethers-rs", default-features = false } ethers-addressbook = { git = "https://github.com/gakonst/ethers-rs", default-features = false } +ethers-providers = { git = "https://github.com/gakonst/ethers-rs", default-features = false } +ethers-solc = { git = "https://github.com/gakonst/ethers-rs", default-features = false } + eyre = { version = "0.6.5", default-features = false } @@ -19,4 +22,5 @@ reqwest = { version = "0.11.8", features = ["json"] } rustc-hex = { version = "2.1.0", default-features = false } serde = "1.0.132" serde_json = { version = "1.0.67", default-features = false } -tokio = { version = "1.15.0", features = ["macros"] } +tokio = { version = "1.12.0", features = ["rt-multi-thread", "macros"] } +rlp = "0.5.1" \ No newline at end of file diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 21d41b491698..5d18a766568e 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -9,6 +9,7 @@ use ethers_core::{ types::*, }; use ethers_etherscan::Client; +use ethers_solc::artifacts::{BytecodeObject, CompactBytecode, CompactContractBytecode}; use eyre::{Result, WrapErr}; use serde::Deserialize; use std::{ @@ -16,6 +17,100 @@ use std::{ env::VarError, }; +use tokio::runtime::{Handle, Runtime}; + +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +pub enum RuntimeOrHandle { + Runtime(Runtime), + Handle(Handle), +} + +impl Default for RuntimeOrHandle { + fn default() -> Self { + Self::new() + } +} + +impl RuntimeOrHandle { + pub fn new() -> RuntimeOrHandle { + match Handle::try_current() { + Ok(handle) => RuntimeOrHandle::Handle(handle), + Err(_) => RuntimeOrHandle::Runtime(Runtime::new().expect("Failed to start runtime")), + } + } + + pub fn block_on(&self, f: F) -> F::Output { + match &self { + RuntimeOrHandle::Runtime(runtime) => runtime.block_on(f), + RuntimeOrHandle::Handle(handle) => tokio::task::block_in_place(|| handle.block_on(f)), + } + } +} + +/// Recursively links bytecode given a target contract artifact name, the bytecode(s) to be linked, +/// a mapping of contract artifact name to bytecode, a dependency mapping, a mutable list that +/// will be filled with the predeploy libraries, initial nonce, and the sender. +pub fn recurse_link<'a>( + // target name + target: String, + // to-be-modified/linked bytecode + target_bytecode: (&'a mut CompactBytecode, &'a mut CompactBytecode), + // Contracts + contracts: &'a BTreeMap, + // fname => Vec<(fname, file, key)> + dependency_tree: &'a BTreeMap>, + // library deployment vector + deployment: &'a mut Vec, + // nonce to start at + init_nonce: U256, + // sender + sender: Address, +) { + // check if we have dependencies + if let Some(dependencies) = dependency_tree.get(&target) { + // for each dependency, try to link + dependencies.iter().for_each(|(next_target, file, key)| { + // get the dependency + let contract = contracts.get(next_target).expect("No target contract").clone(); + let mut next_target_bytecode = contract.bytecode.expect("No target bytecode"); + let mut next_target_runtime_bytecode = contract + .deployed_bytecode + .expect("No target runtime bytecode") + .bytecode + .expect("No target runtime"); + + // make sure dependency is fully linked + if let Some(deps) = dependency_tree.get(&target) { + if !deps.is_empty() { + // actually link the nested dependencies to this dependency + recurse_link( + next_target.to_string(), + (&mut next_target_bytecode, &mut next_target_runtime_bytecode), + contracts, + dependency_tree, + deployment, + init_nonce, + sender, + ); + } + } + + // calculate the address for linking this dependency + let addr = + ethers_core::utils::get_contract_address(sender, init_nonce + deployment.len()); + + // link the dependency to the target + target_bytecode.0.link(file.clone(), key.clone(), addr); + target_bytecode.1.link(file, key, addr); + + // push the dependency into the library deployment vector + deployment + .push(next_target_bytecode.object.into_bytes().expect("Bytecode should be linked")); + }); + } +} + const BASE_TX_COST: u64 = 21000; /// Helper trait for converting types to Functions. Helpful for allowing the `call` @@ -617,6 +712,105 @@ pub fn abi_to_solidity(contract_abi: &Abi, mut contract_name: &str) -> Result { + pub contract: CompactContractBytecode, + pub known_contracts: &'a mut BTreeMap, + pub fname: String, + pub extra: &'a mut U, + pub dependencies: Vec, +} + +pub fn link( + contracts: &BTreeMap, + known_contracts: &mut BTreeMap, + sender: Address, + extra: &mut U, + link_key_construction: impl Fn(String, String) -> (String, String, String), + post_link: impl Fn(PostLinkInput) -> eyre::Result<()>, +) -> eyre::Result<()> { + // we dont use mainnet state for evm_opts.sender so this will always be 1 + // I am leaving this here so that in the future if this needs to change, + // its easy to find. + let nonce = U256::one(); + + // create a mapping of fname => Vec<(fname, file, key)>, + let link_tree: BTreeMap> = contracts + .iter() + .map(|(fname, contract)| { + ( + fname.to_string(), + contract + .all_link_references() + .iter() + .flat_map(|(file, link)| { + link.keys() + .map(|key| link_key_construction(file.to_string(), key.to_string())) + }) + .collect::>(), + ) + }) + .collect(); + + for fname in contracts.keys() { + let (abi, maybe_deployment_bytes, maybe_runtime) = if let Some(c) = contracts.get(fname) { + (c.abi.as_ref(), c.bytecode.as_ref(), c.deployed_bytecode.as_ref()) + } else { + (None, None, None) + }; + if let (Some(abi), Some(bytecode), Some(runtime)) = + (abi, maybe_deployment_bytes, maybe_runtime) + { + // we are going to mutate, but library contract addresses may change based on + // the test so we clone + let mut target_bytecode = bytecode.clone(); + let mut rt = runtime.clone(); + let mut target_bytecode_runtime = rt.bytecode.expect("No target runtime").clone(); + + // instantiate a vector that gets filled with library deployment bytecode + let mut dependencies = vec![]; + + match bytecode.object { + BytecodeObject::Unlinked(_) => { + // link needed + recurse_link( + fname.to_string(), + (&mut target_bytecode, &mut target_bytecode_runtime), + contracts, + &link_tree, + &mut dependencies, + nonce, + sender, + ); + } + BytecodeObject::Bytecode(ref bytes) => { + if bytes.as_ref().is_empty() { + // abstract, skip + continue + } + } + } + + rt.bytecode = Some(target_bytecode_runtime); + let tc = CompactContractBytecode { + abi: Some(abi.clone()), + bytecode: Some(target_bytecode), + deployed_bytecode: Some(rt), + }; + + let post_link_input = PostLinkInput { + contract: tc, + known_contracts, + fname: fname.to_string(), + extra, + dependencies, + }; + + post_link(post_link_input)?; + } + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*;