Ensure a consistent chain ID between a signer and provider in SignerMiddleware (#1095)
* feat(middleware): fetch chainid from middleware Require SignerMiddleware to fetch the inner middleware's to set for the signer. SignerMiddleware now requires an instantiated middleware with an accessible get_chainid method. * ci: update ganache newer ganache version is needed to specify the ganache provider chain id in tests * set SignerMiddleware constructor return to result * create new method for pulling chainid * add consistent chainid CHANGELOG entry * remove awaits * switch test_derive_eip712 to use consistent signer * remove gas estimation equality assert in deploy_and_call_contract - updated version of ganache no longer returns the same value for gas estimation and gas_used * revert with_signer to non-async non-result * cargo fmt * expand SignerMiddleware::new comment * remove doc indent
This commit is contained in:
parent
9206efa46c
commit
1d14f9d26e
|
@ -21,12 +21,12 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install ganache-cli
|
- name: Set up node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 10
|
node-version: 10
|
||||||
- name: Install ganache
|
- name: Install ganache
|
||||||
run: npm install -g ganache-cli
|
run: npm install -g ganache
|
||||||
- name: Install Solc
|
- name: Install Solc
|
||||||
run: |
|
run: |
|
||||||
mkdir -p "$HOME/bin"
|
mkdir -p "$HOME/bin"
|
||||||
|
@ -66,13 +66,13 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install ganache-cli
|
- name: Set up node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 10
|
node-version: 10
|
||||||
# TODO: can we combine these shared steps in github actions?
|
# TODO: can we combine these shared steps in github actions?
|
||||||
- name: Install ganache
|
- name: Install ganache
|
||||||
run: npm install -g ganache-cli
|
run: npm install -g ganache
|
||||||
- name: Install Solc
|
- name: Install Solc
|
||||||
run: |
|
run: |
|
||||||
mkdir -p "$HOME/bin"
|
mkdir -p "$HOME/bin"
|
||||||
|
@ -183,12 +183,12 @@ jobs:
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Checkout sources
|
# - name: Checkout sources
|
||||||
# uses: actions/checkout@v2
|
# uses: actions/checkout@v2
|
||||||
# - name: Install ganache-cli
|
# - name: Set up node
|
||||||
# uses: actions/setup-node@v1
|
# uses: actions/setup-node@v1
|
||||||
# with:
|
# with:
|
||||||
# node-version: 10
|
# node-version: 10
|
||||||
# - name: Install ganache
|
# - name: Install ganache
|
||||||
# run: npm install -g ganache-cli
|
# run: npm install -g ganache
|
||||||
# - name: Install Solc
|
# - name: Install Solc
|
||||||
# run: |
|
# run: |
|
||||||
# mkdir -p "$HOME/bin"
|
# mkdir -p "$HOME/bin"
|
||||||
|
|
|
@ -234,6 +234,9 @@
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
- Ensure a consistent chain ID between a Signer and Provider in SignerMiddleware
|
||||||
|
[#1095](https://gakonst/ethers-rs/pull/1095)
|
||||||
|
|
||||||
### 0.6.0
|
### 0.6.0
|
||||||
|
|
||||||
- add the missing constructor for `Timelag` middleware via
|
- add the missing constructor for `Timelag` middleware via
|
||||||
|
|
|
@ -69,7 +69,6 @@ mod eth_tests {
|
||||||
assert_eq!(last_sender.clone().call().await.unwrap(), addr2);
|
assert_eq!(last_sender.clone().call().await.unwrap(), addr2);
|
||||||
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
||||||
assert_eq!(tx.input, calldata);
|
assert_eq!(tx.input, calldata);
|
||||||
assert_eq!(tx_receipt.gas_used.unwrap(), gas_estimate);
|
|
||||||
|
|
||||||
// we can also call contract methods at other addresses with the `at` call
|
// we can also call contract methods at other addresses with the `at` call
|
||||||
// (useful when interacting with multiple ERC20s for example)
|
// (useful when interacting with multiple ERC20s for example)
|
||||||
|
@ -579,7 +578,8 @@ mod eth_tests {
|
||||||
.expect("failed to instantiate provider from ganache endpoint")
|
.expect("failed to instantiate provider from ganache endpoint")
|
||||||
.interval(Duration::from_millis(10u64));
|
.interval(Duration::from_millis(10u64));
|
||||||
|
|
||||||
let client = SignerMiddleware::new(provider, wallet.clone());
|
let client =
|
||||||
|
SignerMiddleware::new_with_provider_chain(provider, wallet.clone()).await.unwrap();
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
|
|
||||||
let factory = ContractFactory::new(abi.clone(), bytecode.clone(), client.clone());
|
let factory = ContractFactory::new(abi.clone(), bytecode.clone(), client.clone());
|
||||||
|
@ -597,8 +597,8 @@ mod eth_tests {
|
||||||
let contract = DeriveEip712Test::new(addr, client.clone());
|
let contract = DeriveEip712Test::new(addr, client.clone());
|
||||||
|
|
||||||
let foo_bar = FooBar {
|
let foo_bar = FooBar {
|
||||||
foo: I256::from(10),
|
foo: I256::from(10u64),
|
||||||
bar: U256::from(20),
|
bar: U256::from(20u64),
|
||||||
fizz: b"fizz".into(),
|
fizz: b"fizz".into(),
|
||||||
buzz: keccak256("buzz"),
|
buzz: keccak256("buzz"),
|
||||||
far: String::from("space"),
|
far: String::from("space"),
|
||||||
|
|
|
@ -59,7 +59,7 @@ use thiserror::Error;
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`Provider`]: ethers_providers::Provider
|
/// [`Signer`]: ethers_signers::Signer
|
||||||
pub struct SignerMiddleware<M, S> {
|
pub struct SignerMiddleware<M, S> {
|
||||||
pub(crate) inner: M,
|
pub(crate) inner: M,
|
||||||
pub(crate) signer: S,
|
pub(crate) signer: S,
|
||||||
|
@ -107,12 +107,23 @@ where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
{
|
{
|
||||||
/// Creates a new client from the provider and signer.
|
/// Creates a new client from the provider and signer.
|
||||||
|
/// Sets the address of this middleware to the address of the signer.
|
||||||
|
/// The chain_id of the signer will not be set to the chain id of the provider. If the signer
|
||||||
|
/// passed here is initialized with a different chain id, then the client may throw errors, or
|
||||||
|
/// methods like `sign_transaction` may error.
|
||||||
|
/// To automatically set the signer's chain id, see `new_with_provider_chain`.
|
||||||
|
///
|
||||||
|
/// [`Middleware`] ethers_providers::Middleware
|
||||||
|
/// [`Signer`] ethers_signers::Signer
|
||||||
pub fn new(inner: M, signer: S) -> Self {
|
pub fn new(inner: M, signer: S) -> Self {
|
||||||
let address = signer.address();
|
let address = signer.address();
|
||||||
SignerMiddleware { inner, signer, address }
|
SignerMiddleware { inner, signer, address }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs and returns the RLP encoding of the signed transaction
|
/// Signs and returns the RLP encoding of the signed transaction.
|
||||||
|
/// If the transaction does not have a chain id set, it sets it to the signer's chain id.
|
||||||
|
/// Returns an error if the transaction's existing chain id does not match the signer's chain
|
||||||
|
/// id.
|
||||||
async fn sign_transaction(
|
async fn sign_transaction(
|
||||||
&self,
|
&self,
|
||||||
tx: TypedTransaction,
|
tx: TypedTransaction,
|
||||||
|
@ -148,6 +159,7 @@ where
|
||||||
&self.signer
|
&self.signer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds a SignerMiddleware with the given Signer.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_signer(&self, signer: S) -> Self
|
pub fn with_signer(&self, signer: S) -> Self
|
||||||
where
|
where
|
||||||
|
@ -160,6 +172,24 @@ where
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new client from the provider and signer.
|
||||||
|
/// Sets the address of this middleware to the address of the signer.
|
||||||
|
/// Sets the chain id of the signer to the chain id of the inner [`Middleware`] passed in,
|
||||||
|
/// using the [`Signer`]'s implementation of with_chain_id.
|
||||||
|
///
|
||||||
|
/// [`Middleware`] ethers_providers::Middleware
|
||||||
|
/// [`Signer`] ethers_signers::Signer
|
||||||
|
pub async fn new_with_provider_chain(
|
||||||
|
inner: M,
|
||||||
|
signer: S,
|
||||||
|
) -> Result<Self, SignerMiddlewareError<M, S>> {
|
||||||
|
let address = signer.address();
|
||||||
|
let chain_id =
|
||||||
|
inner.get_chainid().await.map_err(|e| SignerMiddlewareError::MiddlewareError(e))?;
|
||||||
|
let signer = signer.with_chain_id(chain_id.as_u64());
|
||||||
|
Ok(SignerMiddleware { inner, signer, address })
|
||||||
|
}
|
||||||
|
|
||||||
fn set_tx_from_if_none(&self, tx: &TypedTransaction) -> TypedTransaction {
|
fn set_tx_from_if_none(&self, tx: &TypedTransaction) -> TypedTransaction {
|
||||||
let mut tx = tx.clone();
|
let mut tx = tx.clone();
|
||||||
if tx.from().is_none() {
|
if tx.from().is_none() {
|
||||||
|
@ -329,7 +359,12 @@ mod tests {
|
||||||
.into();
|
.into();
|
||||||
let chain_id = 1u64;
|
let chain_id = 1u64;
|
||||||
|
|
||||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
// Signer middlewares now rely on a working provider which it can query the chain id from,
|
||||||
|
// so we make sure ganache is started with the chain id that the expected tx was signed
|
||||||
|
// with
|
||||||
|
let ganache =
|
||||||
|
Ganache::new().args(vec!["--chain.chainId".to_string(), chain_id.to_string()]).spawn();
|
||||||
|
let provider = Provider::try_from(ganache.endpoint()).unwrap();
|
||||||
let key = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
|
let key = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
|
||||||
.parse::<LocalWallet>()
|
.parse::<LocalWallet>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -348,6 +383,50 @@ mod tests {
|
||||||
assert_eq!(tx, expected_rlp);
|
assert_eq!(tx, expected_rlp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ganache_consistent_chainid() {
|
||||||
|
let ganache = Ganache::new().spawn();
|
||||||
|
let provider = Provider::try_from(ganache.endpoint()).unwrap();
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
|
assert_eq!(chain_id, U256::from(1337));
|
||||||
|
|
||||||
|
// Intentionally do not set the chain id here so we ensure that the signer pulls the
|
||||||
|
// provider's chain id.
|
||||||
|
let key = LocalWallet::new(&mut rand::thread_rng());
|
||||||
|
|
||||||
|
// combine the provider and wallet and test that the chain id is the same for both the
|
||||||
|
// signer returned by the middleware and through the middleware itself.
|
||||||
|
let client = SignerMiddleware::new_with_provider_chain(provider, key).await.unwrap();
|
||||||
|
let middleware_chainid = client.get_chainid().await.unwrap();
|
||||||
|
assert_eq!(chain_id, middleware_chainid);
|
||||||
|
|
||||||
|
let signer = client.signer();
|
||||||
|
let signer_chainid = signer.chain_id();
|
||||||
|
assert_eq!(chain_id.as_u64(), signer_chainid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ganache_consistent_chainid_not_default() {
|
||||||
|
let ganache = Ganache::new().args(vec!["--chain.chainId", "13371337"]).spawn();
|
||||||
|
let provider = Provider::try_from(ganache.endpoint()).unwrap();
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
|
assert_eq!(chain_id, U256::from(13371337));
|
||||||
|
|
||||||
|
// Intentionally do not set the chain id here so we ensure that the signer pulls the
|
||||||
|
// provider's chain id.
|
||||||
|
let key = LocalWallet::new(&mut rand::thread_rng());
|
||||||
|
|
||||||
|
// combine the provider and wallet and test that the chain id is the same for both the
|
||||||
|
// signer returned by the middleware and through the middleware itself.
|
||||||
|
let client = SignerMiddleware::new_with_provider_chain(provider, key).await.unwrap();
|
||||||
|
let middleware_chainid = client.get_chainid().await.unwrap();
|
||||||
|
assert_eq!(chain_id, middleware_chainid);
|
||||||
|
|
||||||
|
let signer = client.signer();
|
||||||
|
let signer_chainid = signer.chain_id();
|
||||||
|
assert_eq!(chain_id.as_u64(), signer_chainid);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn handles_tx_from_field() {
|
async fn handles_tx_from_field() {
|
||||||
let ganache = Ganache::new().spawn();
|
let ganache = Ganache::new().spawn();
|
||||||
|
@ -361,7 +440,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let client = SignerMiddleware::new(provider, key);
|
let client = SignerMiddleware::new_with_provider_chain(provider, key).await.unwrap();
|
||||||
|
|
||||||
let request = TransactionRequest::new();
|
let request = TransactionRequest::new();
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,10 @@ async fn using_gas_oracle() {
|
||||||
// connect to the network
|
// connect to the network
|
||||||
let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
|
let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
|
||||||
|
|
||||||
|
// this is set because ganache now sets 875000000 as the first block's base fee
|
||||||
|
let base_fee = 875000000;
|
||||||
// assign a gas oracle to use
|
// assign a gas oracle to use
|
||||||
let gas_oracle = FakeGasOracle { gas_price: 1337.into() };
|
let gas_oracle = FakeGasOracle { gas_price: (base_fee + 1337).into() };
|
||||||
let expected_gas_price = gas_oracle.fetch().await.unwrap();
|
let expected_gas_price = gas_oracle.fetch().await.unwrap();
|
||||||
|
|
||||||
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
let provider = GasOracleMiddleware::new(provider, gas_oracle);
|
||||||
|
|
|
@ -35,8 +35,7 @@ async fn send_eth() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.interval(Duration::from_millis(10u64));
|
.interval(Duration::from_millis(10u64));
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||||
let wallet = wallet.with_chain_id(chain_id);
|
let provider = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
||||||
let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
// craft the transaction
|
// craft the transaction
|
||||||
let tx = TransactionRequest::new().to(wallet2.address()).value(10000).chain_id(chain_id);
|
let tx = TransactionRequest::new().to(wallet2.address()).value(10000).chain_id(chain_id);
|
||||||
|
@ -168,7 +167,8 @@ async fn send_transaction_handles_tx_from_field() {
|
||||||
|
|
||||||
// connect to the network
|
// connect to the network
|
||||||
let provider = Provider::try_from(ganache.endpoint()).unwrap();
|
let provider = Provider::try_from(ganache.endpoint()).unwrap();
|
||||||
let provider = SignerMiddleware::new(provider, signer.clone());
|
let provider =
|
||||||
|
SignerMiddleware::new_with_provider_chain(provider, signer.clone()).await.unwrap();
|
||||||
|
|
||||||
// sending a TransactionRequest with a from field of None should result
|
// sending a TransactionRequest with a from field of None should result
|
||||||
// in a transaction from the signer address
|
// in a transaction from the signer address
|
||||||
|
@ -231,7 +231,7 @@ async fn deploy_and_call_contract() {
|
||||||
.parse::<LocalWallet>()
|
.parse::<LocalWallet>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_chain_id(chain_id);
|
.with_chain_id(chain_id);
|
||||||
let client = SignerMiddleware::new(provider, wallet);
|
let client = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
|
|
||||||
let factory = ContractFactory::new(abi, bytecode, client);
|
let factory = ContractFactory::new(abi, bytecode, client);
|
||||||
|
|
Loading…
Reference in New Issue