commit
7c3ee09b90
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -130,6 +130,13 @@
|
||||||
|
|
||||||
## ethers-providers
|
## ethers-providers
|
||||||
|
|
||||||
|
### Unreleased
|
||||||
|
|
||||||
|
- Add support for basic and bearer authentication in http and non-wasm websockets.
|
||||||
|
[829](https://github.com/gakonst/ethers-rs/pull/829)
|
||||||
|
- Export `ethers_providers::IpcError` and `ethers_providers::QuorumError`
|
||||||
|
[1012](https://github.com/gakonst/ethers-rs/pull/1012)
|
||||||
|
|
||||||
### 0.6.0
|
### 0.6.0
|
||||||
|
|
||||||
- re-export error types for `Http` and `Ws` providers in
|
- re-export error types for `Http` and `Ws` providers in
|
||||||
|
@ -144,11 +151,6 @@
|
||||||
- Add support for `evm_snapshot` and `evm_revert` dev RPC methods.
|
- Add support for `evm_snapshot` and `evm_revert` dev RPC methods.
|
||||||
[640](https://github.com/gakonst/ethers-rs/pull/640)
|
[640](https://github.com/gakonst/ethers-rs/pull/640)
|
||||||
|
|
||||||
### Unreleased
|
|
||||||
|
|
||||||
- Add support for basic and bearer authentication in http and non-wasm websockets.
|
|
||||||
[829](https://github.com/gakonst/ethers-rs/pull/829)
|
|
||||||
|
|
||||||
### 0.5.3
|
### 0.5.3
|
||||||
|
|
||||||
- Expose `ens` module [#435](https://github.com/gakonst/ethers-rs/pull/435)
|
- Expose `ens` module [#435](https://github.com/gakonst/ethers-rs/pull/435)
|
||||||
|
|
|
@ -1322,6 +1322,7 @@ dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
"http",
|
"http",
|
||||||
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -2666,9 +2667,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50"
|
checksum = "57c038cb5319b9c704bf9c227c261d275bfec0ad438118a2787ce47944fb228b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"ctor",
|
"ctor",
|
||||||
|
@ -3575,7 +3576,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svm-rs"
|
name = "svm-rs"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
source = "git+https://github.com/roynalnaruto/svm-rs#8e33f55fa2a2afb937749e31b2ffa42600bfe216"
|
source = "git+https://github.com/roynalnaruto/svm-rs#ae79a29f5bde08f1991f981456253fa5b6859047"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
|
|
|
@ -709,8 +709,9 @@ fn check_file_in_dir(dir: &Path, file_name: &str, expected_contents: &[u8]) -> R
|
||||||
let file_path = dir.join(file_name);
|
let file_path = dir.join(file_name);
|
||||||
eyre::ensure!(file_path.is_file(), "Not a file: {}", file_path.display());
|
eyre::ensure!(file_path.is_file(), "Not a file: {}", file_path.display());
|
||||||
|
|
||||||
let contents = fs::read(file_path).expect("Unable to read file");
|
let contents = fs::read(&file_path).expect("Unable to read file");
|
||||||
eyre::ensure!(contents == expected_contents, "file contents do not match");
|
eyre::ensure!(contents == expected_contents, format!("The contents of `{}` do not match the expected output of the newest `ethers::Abigen` version.\
|
||||||
|
This indicates that the existing bindings are outdated and need to be generated again.", file_path.display()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ impl VerifyContract {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.constructor_arguments = constructor_arguments.map(|s| {
|
self.constructor_arguments = constructor_arguments.map(|s| {
|
||||||
s.into()
|
s.into()
|
||||||
|
.trim()
|
||||||
// TODO is this correct?
|
// TODO is this correct?
|
||||||
.trim_start_matches("0x")
|
.trim_start_matches("0x")
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use ethers_core::types::{
|
use ethers_core::types::{
|
||||||
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Signature,
|
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
|
||||||
|
Address, BlockId, Bytes, Signature, U256,
|
||||||
};
|
};
|
||||||
use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction};
|
use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction};
|
||||||
use ethers_signers::Signer;
|
use ethers_signers::Signer;
|
||||||
|
@ -158,6 +159,14 @@ where
|
||||||
this.signer = signer;
|
this.signer = signer;
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_tx_from_if_none(&self, tx: &TypedTransaction) -> TypedTransaction {
|
||||||
|
let mut tx = tx.clone();
|
||||||
|
if tx.from().is_none() {
|
||||||
|
tx.set_from(self.address);
|
||||||
|
}
|
||||||
|
tx
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
@ -264,6 +273,32 @@ where
|
||||||
) -> Result<Signature, Self::Error> {
|
) -> Result<Signature, Self::Error> {
|
||||||
self.signer.sign_message(data.into()).await.map_err(SignerMiddlewareError::SignerError)
|
self.signer.sign_message(data.into()).await.map_err(SignerMiddlewareError::SignerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, Self::Error> {
|
||||||
|
let tx = self.set_tx_from_if_none(tx);
|
||||||
|
self.inner.estimate_gas(&tx).await.map_err(SignerMiddlewareError::MiddlewareError)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_access_list(
|
||||||
|
&self,
|
||||||
|
tx: &TypedTransaction,
|
||||||
|
block: Option<BlockId>,
|
||||||
|
) -> Result<AccessListWithGasUsed, Self::Error> {
|
||||||
|
let tx = self.set_tx_from_if_none(tx);
|
||||||
|
self.inner
|
||||||
|
.create_access_list(&tx, block)
|
||||||
|
.await
|
||||||
|
.map_err(SignerMiddlewareError::MiddlewareError)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn call(
|
||||||
|
&self,
|
||||||
|
tx: &TypedTransaction,
|
||||||
|
block: Option<BlockId>,
|
||||||
|
) -> Result<Bytes, Self::Error> {
|
||||||
|
let tx = self.set_tx_from_if_none(tx);
|
||||||
|
self.inner().call(&tx, block).await.map_err(SignerMiddlewareError::MiddlewareError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, not(feature = "celo"), not(target_arch = "wasm32")))]
|
#[cfg(all(test, not(feature = "celo"), not(target_arch = "wasm32")))]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use ethers_middleware::{
|
||||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||||
signer::SignerMiddleware,
|
signer::SignerMiddleware,
|
||||||
};
|
};
|
||||||
use ethers_providers::{Middleware, Provider, Ws};
|
use ethers_providers::Middleware;
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -12,10 +12,8 @@ use std::time::Duration;
|
||||||
#[ignore]
|
#[ignore]
|
||||||
async fn gas_escalator_live() {
|
async fn gas_escalator_live() {
|
||||||
// connect to ropsten for getting bad block times
|
// connect to ropsten for getting bad block times
|
||||||
let ws = Ws::connect("wss://ropsten.infura.io/ws/v3/fd8b88b56aa84f6da87b60f5441d6778")
|
let provider = ethers_providers::ROPSTEN.ws().await;
|
||||||
.await
|
let provider = provider.interval(Duration::from_millis(2000u64));
|
||||||
.unwrap();
|
|
||||||
let provider = Provider::new(ws).interval(Duration::from_millis(2000u64));
|
|
||||||
let wallet = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169"
|
let wallet = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169"
|
||||||
.parse::<LocalWallet>()
|
.parse::<LocalWallet>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -4,14 +4,11 @@
|
||||||
async fn nonce_manager() {
|
async fn nonce_manager() {
|
||||||
use ethers_core::types::*;
|
use ethers_core::types::*;
|
||||||
use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
|
use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::Middleware;
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
use std::{convert::TryFrom, time::Duration};
|
use std::time::Duration;
|
||||||
|
|
||||||
let provider =
|
let provider = ethers_providers::RINKEBY.provider().interval(Duration::from_millis(2000u64));
|
||||||
Provider::<Http>::try_from("https://rinkeby.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778")
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(2000u64));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||||
|
|
||||||
let wallet = std::env::var("RINKEBY_PRIVATE_KEY")
|
let wallet = std::env::var("RINKEBY_PRIVATE_KEY")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider};
|
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, RINKEBY};
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
types::{BlockNumber, TransactionRequest},
|
types::{BlockNumber, TransactionRequest},
|
||||||
|
@ -8,7 +8,7 @@ use ethers_core::{
|
||||||
use ethers_middleware::signer::SignerMiddleware;
|
use ethers_middleware::signer::SignerMiddleware;
|
||||||
use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
|
use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{convert::TryFrom, sync::atomic::AtomicU8, time::Duration};
|
use std::{convert::TryFrom, iter::Cycle, sync::atomic::AtomicU8, time::Duration};
|
||||||
|
|
||||||
static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
|
static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
|
||||||
TestWallets {
|
TestWallets {
|
||||||
|
@ -54,10 +54,7 @@ async fn send_eth() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
async fn pending_txs_with_confirmations_testnet() {
|
async fn pending_txs_with_confirmations_testnet() {
|
||||||
let provider =
|
let provider = RINKEBY.provider().interval(Duration::from_millis(3000));
|
||||||
Provider::<Http>::try_from("https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(3000));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap();
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
||||||
let address = wallet.address();
|
let address = wallet.address();
|
||||||
|
@ -72,11 +69,7 @@ use ethers_core::types::{Address, Eip1559TransactionRequest};
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
async fn websocket_pending_txs_with_confirmations_testnet() {
|
async fn websocket_pending_txs_with_confirmations_testnet() {
|
||||||
let provider =
|
let provider = RINKEBY.ws().await.interval(Duration::from_millis(3000));
|
||||||
Provider::connect("wss://rinkeby.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27")
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(3000));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap();
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
||||||
let address = wallet.address();
|
let address = wallet.address();
|
||||||
|
@ -97,9 +90,7 @@ async fn generic_pending_txs_test<M: Middleware>(provider: M, who: Address) {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
async fn typed_txs() {
|
async fn typed_txs() {
|
||||||
let provider =
|
let provider = RINKEBY.provider();
|
||||||
Provider::<Http>::try_from("https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap();
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
||||||
|
|
|
@ -39,6 +39,7 @@ tracing = { version = "0.1.32", default-features = false }
|
||||||
tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future"] }
|
tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future"] }
|
||||||
|
|
||||||
bytes = { version = "1.1.0", default-features = false, optional = true }
|
bytes = { version = "1.1.0", default-features = false, optional = true }
|
||||||
|
once_cell = "1.10.0"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
# tokio
|
# tokio
|
||||||
|
|
|
@ -657,3 +657,63 @@ pub trait CeloMiddleware: Middleware {
|
||||||
self.provider().get_validators_bls_public_keys(block_id).await.map_err(FromErr::from)
|
self.provider().get_validators_bls_public_keys(block_id).await.map_err(FromErr::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use test_provider::{GOERLI, MAINNET, RINKEBY, ROPSTEN};
|
||||||
|
|
||||||
|
/// Pre-instantiated Infura HTTP clients which rotate through multiple API keys
|
||||||
|
/// to prevent rate limits
|
||||||
|
pub mod test_provider {
|
||||||
|
use super::*;
|
||||||
|
use crate::Http;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::{convert::TryFrom, iter::Cycle, slice::Iter, sync::Mutex};
|
||||||
|
|
||||||
|
// List of infura keys to rotate through so we don't get rate limited
|
||||||
|
const INFURA_KEYS: &[&str] = &[
|
||||||
|
"6770454bc6ea42c58aac12978531b93f",
|
||||||
|
"7a8769b798b642f6933f2ed52042bd70",
|
||||||
|
"631fd9a6539644088297dc605d35fff3",
|
||||||
|
"16a8be88795540b9b3903d8de0f7baa5",
|
||||||
|
"f4a0bdad42674adab5fc0ac077ffab2b",
|
||||||
|
"5c812e02193c4ba793f8c214317582bd",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static RINKEBY: Lazy<TestProvider> =
|
||||||
|
Lazy::new(|| TestProvider::new(INFURA_KEYS, "rinkeby"));
|
||||||
|
pub static MAINNET: Lazy<TestProvider> =
|
||||||
|
Lazy::new(|| TestProvider::new(INFURA_KEYS, "mainnet"));
|
||||||
|
pub static GOERLI: Lazy<TestProvider> = Lazy::new(|| TestProvider::new(INFURA_KEYS, "goerli"));
|
||||||
|
pub static ROPSTEN: Lazy<TestProvider> =
|
||||||
|
Lazy::new(|| TestProvider::new(INFURA_KEYS, "ropsten"));
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TestProvider {
|
||||||
|
network: String,
|
||||||
|
keys: Mutex<Cycle<Iter<'static, &'static str>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestProvider {
|
||||||
|
pub fn new(keys: &'static [&'static str], network: &str) -> Self {
|
||||||
|
Self { keys: Mutex::new(keys.iter().cycle()), network: network.to_owned() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn provider(&self) -> Provider<Http> {
|
||||||
|
let url = format!(
|
||||||
|
"https://{}.infura.io/v3/{}",
|
||||||
|
self.network,
|
||||||
|
self.keys.lock().unwrap().next().unwrap()
|
||||||
|
);
|
||||||
|
Provider::try_from(url.as_str()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ws")]
|
||||||
|
pub async fn ws(&self) -> Provider<crate::Ws> {
|
||||||
|
let url = format!(
|
||||||
|
"wss://{}.infura.io/ws/v3/{}",
|
||||||
|
self.network,
|
||||||
|
self.keys.lock().unwrap().next().unwrap()
|
||||||
|
);
|
||||||
|
Provider::connect(url.as_str()).await.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1488,12 +1488,10 @@ mod tests {
|
||||||
};
|
};
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
const INFURA: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
// Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2
|
// Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2
|
||||||
async fn mainnet_resolve_name() {
|
async fn mainnet_resolve_name() {
|
||||||
let provider = Provider::<HttpProvider>::try_from(INFURA).unwrap();
|
let provider = crate::test_provider::MAINNET.provider();
|
||||||
|
|
||||||
let addr = provider.resolve_name("registrar.firefly.eth").await.unwrap();
|
let addr = provider.resolve_name("registrar.firefly.eth").await.unwrap();
|
||||||
assert_eq!(addr, "6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap());
|
assert_eq!(addr, "6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap());
|
||||||
|
@ -1508,7 +1506,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
// Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2
|
// Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2
|
||||||
async fn mainnet_lookup_address() {
|
async fn mainnet_lookup_address() {
|
||||||
let provider = Provider::<HttpProvider>::try_from(INFURA).unwrap();
|
let provider = crate::MAINNET.provider();
|
||||||
|
|
||||||
let name = provider
|
let name = provider
|
||||||
.lookup_address("6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap())
|
.lookup_address("6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap())
|
||||||
|
@ -1525,7 +1523,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn mainnet_resolve_avatar() {
|
async fn mainnet_resolve_avatar() {
|
||||||
let provider = Provider::<HttpProvider>::try_from(INFURA).unwrap();
|
let provider = crate::MAINNET.provider();
|
||||||
|
|
||||||
for (ens_name, res) in &[
|
for (ens_name, res) in &[
|
||||||
// HTTPS
|
// HTTPS
|
||||||
|
|
|
@ -22,7 +22,7 @@ macro_rules! if_not_wasm {
|
||||||
#[cfg(all(target_family = "unix", feature = "ipc"))]
|
#[cfg(all(target_family = "unix", feature = "ipc"))]
|
||||||
mod ipc;
|
mod ipc;
|
||||||
#[cfg(all(target_family = "unix", feature = "ipc"))]
|
#[cfg(all(target_family = "unix", feature = "ipc"))]
|
||||||
pub use ipc::Ipc;
|
pub use ipc::{Ipc, IpcError};
|
||||||
|
|
||||||
mod http;
|
mod http;
|
||||||
pub use self::http::{ClientError as HttpClientError, Provider as Http};
|
pub use self::http::{ClientError as HttpClientError, Provider as Http};
|
||||||
|
@ -34,7 +34,7 @@ pub use ws::{ClientError as WsClientError, Ws};
|
||||||
|
|
||||||
mod quorum;
|
mod quorum;
|
||||||
pub(crate) use quorum::JsonRpcClientWrapper;
|
pub(crate) use quorum::JsonRpcClientWrapper;
|
||||||
pub use quorum::{Quorum, QuorumProvider, WeightedProvider};
|
pub use quorum::{Quorum, QuorumError, QuorumProvider, WeightedProvider};
|
||||||
|
|
||||||
mod mock;
|
mod mock;
|
||||||
pub use mock::{MockError, MockProvider};
|
pub use mock::{MockError, MockProvider};
|
||||||
|
|
|
@ -62,8 +62,7 @@ if_not_wasm! {
|
||||||
use super::Authorization;
|
use super::Authorization;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
use http::Request as HttpRequest;
|
use http::Request as HttpRequest;
|
||||||
use http::Uri;
|
use tungstenite::client::IntoClientRequest;
|
||||||
use std::str::FromStr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pending = oneshot::Sender<Result<serde_json::Value, JsonRpcError>>;
|
type Pending = oneshot::Sender<Result<serde_json::Value, JsonRpcError>>;
|
||||||
|
@ -137,9 +136,7 @@ impl Ws {
|
||||||
|
|
||||||
/// Initializes a new WebSocket Client
|
/// Initializes a new WebSocket Client
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub async fn connect(
|
pub async fn connect(url: impl IntoClientRequest + Unpin) -> Result<Self, ClientError> {
|
||||||
url: impl tungstenite::client::IntoClientRequest + Unpin,
|
|
||||||
) -> Result<Self, ClientError> {
|
|
||||||
let (ws, _) = connect_async(url).await?;
|
let (ws, _) = connect_async(url).await?;
|
||||||
Ok(Self::new(ws))
|
Ok(Self::new(ws))
|
||||||
}
|
}
|
||||||
|
@ -147,11 +144,10 @@ impl Ws {
|
||||||
/// Initializes a new WebSocket Client with authentication
|
/// Initializes a new WebSocket Client with authentication
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub async fn connect_with_auth(
|
pub async fn connect_with_auth(
|
||||||
uri: impl AsRef<str> + Unpin,
|
uri: impl IntoClientRequest + Unpin,
|
||||||
auth: Authorization,
|
auth: Authorization,
|
||||||
) -> Result<Self, ClientError> {
|
) -> Result<Self, ClientError> {
|
||||||
let mut request: HttpRequest<()> =
|
let mut request: HttpRequest<()> = uri.into_client_request()?;
|
||||||
HttpRequest::builder().method("GET").uri(Uri::from_str(uri.as_ref())?).body(())?;
|
|
||||||
|
|
||||||
let mut auth_value = http::HeaderValue::from_str(&auth.to_string())?;
|
let mut auth_value = http::HeaderValue::from_str(&auth.to_string())?;
|
||||||
auth_value.set_sensitive(true);
|
auth_value.set_sensitive(true);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider, RINKEBY};
|
||||||
use std::{convert::TryFrom, time::Duration};
|
use std::{convert::TryFrom, time::Duration};
|
||||||
|
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
|
@ -12,10 +12,7 @@ mod eth_tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn non_existing_data_works() {
|
async fn non_existing_data_works() {
|
||||||
let provider = Provider::<Http>::try_from(
|
let provider = RINKEBY.provider();
|
||||||
"https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(provider.get_transaction(H256::zero()).await.unwrap().is_none());
|
assert!(provider.get_transaction(H256::zero()).await.unwrap().is_none());
|
||||||
assert!(provider.get_transaction_receipt(H256::zero()).await.unwrap().is_none());
|
assert!(provider.get_transaction_receipt(H256::zero()).await.unwrap().is_none());
|
||||||
|
@ -25,10 +22,7 @@ mod eth_tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn client_version() {
|
async fn client_version() {
|
||||||
let provider = Provider::<Http>::try_from(
|
let provider = RINKEBY.provider();
|
||||||
"https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// e.g., Geth/v1.10.6-omnibus-1af33248/linux-amd64/go1.16.6
|
// e.g., Geth/v1.10.6-omnibus-1af33248/linux-amd64/go1.16.6
|
||||||
assert!(provider
|
assert!(provider
|
||||||
|
@ -41,11 +35,7 @@ mod eth_tests {
|
||||||
// Without TLS this would error with "TLS Support not compiled in"
|
// Without TLS this would error with "TLS Support not compiled in"
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ssl_websocket() {
|
async fn ssl_websocket() {
|
||||||
use ethers_providers::Ws;
|
let provider = RINKEBY.ws().await;
|
||||||
let ws = Ws::connect("wss://rinkeby.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27")
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let provider = Provider::new(ws);
|
|
||||||
let _number = provider.get_block_number().await.unwrap();
|
let _number = provider.get_block_number().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,10 +85,7 @@ mod eth_tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn eip1559_fee_estimation() {
|
async fn eip1559_fee_estimation() {
|
||||||
let provider = Provider::<Http>::try_from(
|
let provider = ethers_providers::MAINNET.provider();
|
||||||
"https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (_max_fee_per_gas, _max_priority_fee_per_gas) =
|
let (_max_fee_per_gas, _max_priority_fee_per_gas) =
|
||||||
provider.estimate_eip1559_fees(None).await.unwrap();
|
provider.estimate_eip1559_fees(None).await.unwrap();
|
||||||
|
|
|
@ -53,7 +53,7 @@ criterion = { version = "0.3", features = ["async_tokio"] }
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]}
|
tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]}
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
pretty_assertions = "1.1.0"
|
pretty_assertions = "1.2.0"
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-cache-2";
|
||||||
/// The file name of the default cache file
|
/// The file name of the default cache file
|
||||||
pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json";
|
pub const SOLIDITY_FILES_CACHE_FILENAME: &str = "solidity-files-cache.json";
|
||||||
|
|
||||||
/// A hardhat compatible cache representation
|
/// A multi version cache file
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct SolFilesCache {
|
pub struct SolFilesCache {
|
||||||
#[serde(rename = "_format")]
|
#[serde(rename = "_format")]
|
||||||
|
@ -593,62 +593,35 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns only dirty sources that:
|
/// Returns only those sources that
|
||||||
/// - are new
|
/// - are new
|
||||||
/// - were changed
|
/// - were changed
|
||||||
/// - their imports were changed
|
/// - their imports were changed
|
||||||
/// - their artifact is missing
|
/// - their artifact is missing
|
||||||
/// This also includes their respective imports
|
|
||||||
fn filter(&mut self, sources: Sources, version: &Version) -> Sources {
|
fn filter(&mut self, sources: Sources, version: &Version) -> Sources {
|
||||||
self.fill_hashes(&sources);
|
self.fill_hashes(&sources);
|
||||||
|
sources
|
||||||
let mut imports_of_dirty = HashSet::new();
|
|
||||||
// separates all source files that fit the criteria (dirty) from those that don't (clean)
|
|
||||||
let (mut dirty_sources, clean_sources) = sources
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(file, source)| self.filter_source(file, source, version))
|
.filter_map(|(file, source)| self.requires_solc(file, source, version))
|
||||||
.fold(
|
.collect()
|
||||||
(Sources::default(), Vec::new()),
|
|
||||||
|(mut dirty_sources, mut clean_sources), source| {
|
|
||||||
if source.dirty {
|
|
||||||
// mark all files that are imported by a dirty file
|
|
||||||
imports_of_dirty.extend(self.edges.all_imported_nodes(source.idx));
|
|
||||||
dirty_sources.insert(source.file, source.source);
|
|
||||||
} else {
|
|
||||||
clean_sources.push(source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(dirty_sources, clean_sources)
|
/// Returns `Some` if the file _needs_ to be compiled and `None` if the artifact can be reu-used
|
||||||
},
|
fn requires_solc(
|
||||||
);
|
&mut self,
|
||||||
|
file: PathBuf,
|
||||||
for clean_source in clean_sources {
|
source: Source,
|
||||||
let FilteredSource { file, source, idx, .. } = clean_source;
|
version: &Version,
|
||||||
if imports_of_dirty.contains(&idx) {
|
) -> Option<(PathBuf, Source)> {
|
||||||
// file is imported by a dirty file
|
|
||||||
dirty_sources.insert(file, source);
|
|
||||||
} else {
|
|
||||||
self.insert_filtered_source(file, source, version.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// track dirty sources internally
|
|
||||||
for (file, source) in dirty_sources.iter() {
|
|
||||||
self.insert_new_cache_entry(file, source, version.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
dirty_sources
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the state of the given source file.
|
|
||||||
fn filter_source(&self, file: PathBuf, source: Source, version: &Version) -> FilteredSource {
|
|
||||||
let idx = self.edges.node_id(&file);
|
|
||||||
if !self.is_dirty(&file, version) &&
|
if !self.is_dirty(&file, version) &&
|
||||||
self.edges.imports(&file).iter().all(|file| !self.is_dirty(file, version))
|
self.edges.imports(&file).iter().all(|file| !self.is_dirty(file, version))
|
||||||
{
|
{
|
||||||
FilteredSource { file, source, idx, dirty: false }
|
self.insert_filtered_source(file, source, version.clone());
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
FilteredSource { file, source, idx, dirty: true }
|
self.insert_new_cache_entry(&file, &source, version.clone());
|
||||||
|
|
||||||
|
Some((file, source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -665,6 +638,10 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only check artifact's existence if the file generated artifacts.
|
||||||
|
// e.g. a solidity file consisting only of import statements (like interfaces that
|
||||||
|
// re-export) do not create artifacts
|
||||||
|
if !entry.artifacts.is_empty() {
|
||||||
if !entry.contains_version(version) {
|
if !entry.contains_version(version) {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"missing linked artifacts for source file `{}` for version \"{}\"",
|
"missing linked artifacts for source file `{}` for version \"{}\"",
|
||||||
|
@ -683,6 +660,7 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
|
||||||
}) {
|
}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// all things match, can be reused
|
// all things match, can be reused
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -701,14 +679,6 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper type to represent the state of a source file
|
|
||||||
struct FilteredSource {
|
|
||||||
file: PathBuf,
|
|
||||||
source: Source,
|
|
||||||
idx: usize,
|
|
||||||
dirty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Abstraction over configured caching which can be either non-existent or an already loaded cache
|
/// Abstraction over configured caching which can be either non-existent or an already loaded cache
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -372,10 +372,17 @@ impl Solc {
|
||||||
pub fn blocking_install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
|
pub fn blocking_install(version: &Version) -> std::result::Result<(), svm::SolcVmError> {
|
||||||
tracing::trace!("blocking installing solc version \"{}\"", version);
|
tracing::trace!("blocking installing solc version \"{}\"", version);
|
||||||
crate::report::solc_installation_start(version);
|
crate::report::solc_installation_start(version);
|
||||||
svm::blocking_install(version)?;
|
match svm::blocking_install(version) {
|
||||||
|
Ok(_) => {
|
||||||
crate::report::solc_installation_success(version);
|
crate::report::solc_installation_success(version);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
crate::report::solc_installation_error(version, &err.to_string());
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify that the checksum for this version of solc is correct. We check against the SHA256
|
/// Verify that the checksum for this version of solc is correct. We check against the SHA256
|
||||||
/// checksum from the build information published by binaries.soliditylang
|
/// checksum from the build information published by binaries.soliditylang
|
||||||
|
|
|
@ -73,6 +73,33 @@
|
||||||
//! file in the VFS under `dapp-bin/library/math.sol`. If the file is not available there, the
|
//! file in the VFS under `dapp-bin/library/math.sol`. If the file is not available there, the
|
||||||
//! source unit name will be passed to the Host Filesystem Loader, which will then look in
|
//! source unit name will be passed to the Host Filesystem Loader, which will then look in
|
||||||
//! `/project/dapp-bin/library/iterable_mapping.sol`
|
//! `/project/dapp-bin/library/iterable_mapping.sol`
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! ### Caching and Change detection
|
||||||
|
//!
|
||||||
|
//! If caching is enabled in the [Project](crate::Project) a cache file will be created upon a
|
||||||
|
//! successful solc build. The [cache file](crate::SolFilesCache) stores metadata for all the files
|
||||||
|
//! that were provided to solc.
|
||||||
|
//! For every file the cache file contains a dedicated [cache
|
||||||
|
//! entry](crate::CacheEntry), which represents the state of the file. A solidity file can contain
|
||||||
|
//! several contracts, for every contract a separate [artifact](crate::Artifact) is emitted.
|
||||||
|
//! Therefor the entry also tracks all artifacts emitted by a file. A solidity file can also be
|
||||||
|
//! compiled with several solc versions.
|
||||||
|
//!
|
||||||
|
//! For example in `A(<=0.8.10) imports C(>0.4.0)` and
|
||||||
|
//! `B(0.8.11) imports C(>0.4.0)`, both `A` and `B` import `C` but there's no solc version that's
|
||||||
|
//! compatible with `A` and `B`, in which case two sets are compiled: [`A`, `C`] and [`B`, `C`].
|
||||||
|
//! This is reflected in the cache entry which tracks the file's artifacts by version.
|
||||||
|
//!
|
||||||
|
//! The cache makes it possible to detect changes during recompilation, so that only the changed,
|
||||||
|
//! dirty, files need to be passed to solc. A file will be considered as dirty if:
|
||||||
|
//! - the file is new, not included in the existing cache
|
||||||
|
//! - the file was modified since the last compiler run, detected by comparing content hashes
|
||||||
|
//! - any of the imported files is dirty
|
||||||
|
//! - the file's artifacts don't exist, were deleted.
|
||||||
|
//!
|
||||||
|
//! Recompiling a project with cache enabled detects all files that meet these criteria and provides
|
||||||
|
//! solc with only these dirty files instead of the entire source set.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
artifact_output::Artifacts,
|
artifact_output::Artifacts,
|
||||||
|
@ -283,7 +310,13 @@ impl CompilerSources {
|
||||||
sources
|
sources
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(solc, (version, sources))| {
|
.map(|(solc, (version, sources))| {
|
||||||
|
tracing::trace!("Filtering {} sources for {}", sources.len(), version);
|
||||||
let sources = cache.filter(sources, &version);
|
let sources = cache.filter(sources, &version);
|
||||||
|
tracing::trace!(
|
||||||
|
"Detected {} dirty sources {:?}",
|
||||||
|
sources.len(),
|
||||||
|
sources.keys()
|
||||||
|
);
|
||||||
(solc, (version, sources))
|
(solc, (version, sources))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -661,7 +661,7 @@ mod tests {
|
||||||
assert_eq!(relative.path.original(), Path::new(&remapping.path));
|
assert_eq!(relative.path.original(), Path::new(&remapping.path));
|
||||||
assert!(relative.path.parent.is_none());
|
assert!(relative.path.parent.is_none());
|
||||||
|
|
||||||
let relative = RelativeRemapping::new(remapping.clone(), "/a/b");
|
let relative = RelativeRemapping::new(remapping, "/a/b");
|
||||||
assert_eq!(relative.to_relative_remapping(), Remapping::from_str("oz/=c/d/").unwrap());
|
assert_eq!(relative.to_relative_remapping(), Remapping::from_str("oz/=c/d/").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
// https://github.com/tokio-rs/tracing/blob/master/tracing-core/src/dispatch.rs
|
// https://github.com/tokio-rs/tracing/blob/master/tracing-core/src/dispatch.rs
|
||||||
|
|
||||||
use crate::{CompilerInput, CompilerOutput, Solc};
|
use crate::{remappings::Remapping, CompilerInput, CompilerOutput, Solc};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
|
@ -99,11 +99,14 @@ pub trait Reporter: 'static {
|
||||||
/// Invoked before a new [`Solc`] bin is installed
|
/// Invoked before a new [`Solc`] bin is installed
|
||||||
fn on_solc_installation_start(&self, _version: &Version) {}
|
fn on_solc_installation_start(&self, _version: &Version) {}
|
||||||
|
|
||||||
/// Invoked before a new [`Solc`] bin was successfully installed
|
/// Invoked after a new [`Solc`] bin was successfully installed
|
||||||
fn on_solc_installation_success(&self, _version: &Version) {}
|
fn on_solc_installation_success(&self, _version: &Version) {}
|
||||||
|
|
||||||
/// Invoked if the import couldn't be resolved
|
/// Invoked after a [`Solc`] installation failed
|
||||||
fn on_unresolved_import(&self, _import: &Path) {}
|
fn on_solc_installation_error(&self, _version: &Version, _error: &str) {}
|
||||||
|
|
||||||
|
/// Invoked if the import couldn't be resolved with these remappings
|
||||||
|
fn on_unresolved_import(&self, _import: &Path, _remappings: &[Remapping]) {}
|
||||||
|
|
||||||
/// If `self` is the same type as the provided `TypeId`, returns an untyped
|
/// If `self` is the same type as the provided `TypeId`, returns an untyped
|
||||||
/// [`NonNull`] pointer to that type. Otherwise, returns `None`.
|
/// [`NonNull`] pointer to that type. Otherwise, returns `None`.
|
||||||
|
@ -166,8 +169,13 @@ pub(crate) fn solc_installation_success(version: &Version) {
|
||||||
get_default(|r| r.reporter.on_solc_installation_success(version));
|
get_default(|r| r.reporter.on_solc_installation_success(version));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unresolved_import(import: &Path) {
|
#[allow(unused)]
|
||||||
get_default(|r| r.reporter.on_unresolved_import(import));
|
pub(crate) fn solc_installation_error(version: &Version, error: &str) {
|
||||||
|
get_default(|r| r.reporter.on_solc_installation_error(version, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unresolved_import(import: &Path, remappings: &[Remapping]) {
|
||||||
|
get_default(|r| r.reporter.on_unresolved_import(import, remappings));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_global() -> Option<&'static Report> {
|
fn get_global() -> Option<&'static Report> {
|
||||||
|
@ -308,8 +316,16 @@ impl Reporter for BasicStdoutReporter {
|
||||||
println!("Successfully installed solc {}", version);
|
println!("Successfully installed solc {}", version);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_unresolved_import(&self, import: &Path) {
|
fn on_solc_installation_error(&self, version: &Version, error: &str) {
|
||||||
println!("Unable to resolve imported file: \"{}\"", import.display());
|
eprintln!("Failed to install solc {}: {}", version, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_unresolved_import(&self, import: &Path, remappings: &[Remapping]) {
|
||||||
|
println!(
|
||||||
|
"Unable to resolve import: \"{}\" with remappings:\n {}",
|
||||||
|
import.display(),
|
||||||
|
remappings.iter().map(|r| r.to_string()).collect::<Vec<_>>().join("\n ")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -274,7 +274,7 @@ impl Graph {
|
||||||
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
crate::report::unresolved_import(import.data());
|
crate::report::unresolved_import(import.data(), &paths.remappings);
|
||||||
tracing::trace!("failed to resolve import component \"{:?}\"", err)
|
tracing::trace!("failed to resolve import component \"{:?}\"", err)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -685,3 +685,51 @@ fn can_recompile_with_changes() {
|
||||||
assert!(compiled.find("A").is_some());
|
assert!(compiled.find("A").is_some());
|
||||||
assert!(compiled.find("B").is_some());
|
assert!(compiled.find("B").is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_recompile_unchanged_with_empty_files() {
|
||||||
|
let tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
||||||
|
tmp.add_source(
|
||||||
|
"A",
|
||||||
|
r#"
|
||||||
|
pragma solidity ^0.8.10;
|
||||||
|
import "./B.sol";
|
||||||
|
contract A {}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tmp.add_source(
|
||||||
|
"B",
|
||||||
|
r#"
|
||||||
|
pragma solidity ^0.8.10;
|
||||||
|
import "./C.sol";
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let c = r#"
|
||||||
|
pragma solidity ^0.8.10;
|
||||||
|
contract C {}
|
||||||
|
"#;
|
||||||
|
tmp.add_source("C", c).unwrap();
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
assert!(compiled.find("A").is_some());
|
||||||
|
assert!(compiled.find("C").is_some());
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(compiled.find("A").is_some());
|
||||||
|
assert!(compiled.find("C").is_some());
|
||||||
|
assert!(compiled.is_unchanged());
|
||||||
|
|
||||||
|
// modify C.sol
|
||||||
|
tmp.add_source("C", format!("{}\n", c)).unwrap();
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
assert!(!compiled.is_unchanged());
|
||||||
|
assert!(compiled.find("A").is_some());
|
||||||
|
assert!(compiled.find("C").is_some());
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ async fn main() -> Result<()> {
|
||||||
let project = Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap();
|
let project = Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap();
|
||||||
// compile the project and get the artifacts
|
// compile the project and get the artifacts
|
||||||
let output = project.compile().unwrap();
|
let output = project.compile().unwrap();
|
||||||
let contract = output.find("SimpleStorage").expect("could not find contract").into_owned();
|
let contract = output.find("SimpleStorage").expect("could not find contract").clone();
|
||||||
let (abi, bytecode, _) = contract.into_parts_or_default();
|
let (abi, bytecode, _) = contract.into_parts_or_default();
|
||||||
|
|
||||||
// 2. instantiate our wallet & ganache
|
// 2. instantiate our wallet & ganache
|
||||||
|
|
Loading…
Reference in New Issue