alloy-rs / alloy-rs/alloy

[Bug] Contract Instance doesn't react properly to re-orgs (nonce issue)

オープン
#2,668 コメント 0 件 リアクション 1 件 担当者 0 名 GitHub で見る
bug
主要言語
Rust
スター
1.3k
フォーク
668
平均マージ
2日 2時間
マージ済み PR(30日)
29

説明

### Component

contract

### What version of Alloy are you on?

latest - v1.0.17

### Operating System

None

### Describe the bug

Whenever a reorg happens the contract instance gets bricked and cannot produce valid transactions (it keeps re-using the old nonce).
Even creating new instances of the contract won't help.

I created a simple example reproducing the issue:

```rust
use alloy::{
node_bindings::Anvil,
primitives::U256,
providers::{ext::AnvilApi, Provider, ProviderBuilder},
signers::local::PrivateKeySigner,
sol,
};

use std::{str::FromStr, time::Duration};

/// alloy's default account 1
pub const PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";

#[tokio::test]
#[allow(clippy::unwrap_used)]
async fn reproduce_alloy_contract_instance_reorg_bug() {
let signer = PrivateKeySigner::from_str(PRIVATE_KEY)
.unwrap_or_else(|err| panic!("Invalid private key: {err}"));

// start anvil
let cmd = vec![
"--base-fee",
"0",
"--gas-limit",
"30000000",
];
let anvil = Anvil::new()
.port(9999u16)
.chain_id(1234)
.args(cmd)
.try_spawn()
.unwrap();
let provider = ProviderBuilder::new()
.wallet(signer.clone())
.connect(&anvil.endpoint())
.await
.unwrap();

// deploy a storage contract
let storage = Storage::deploy(&provider, U256::from(1)).await.unwrap();
assert_eq!(storage.get().call().await.unwrap(), U256::from(1));

// updating the value works fine
_ = storage.set(U256::from(2)).send().await.unwrap();
assert_eq!(storage.get().call().await.unwrap(), U256::from(2));

// reorg anvil
provider.anvil_rollback(Some(1)).await.unwrap();

// wait for reorg to take effect
tokio::time::sleep(Duration::from_secs(2)).await;

// stored value should be reverted to the original value
assert_eq!(storage.get().call().await.unwrap(), U256::from(1));

//
// ATTENTION BELOW, here's where the bug can be observed

// ISSUE 1.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// now try setting a new value (this will fail, but it shouldn't ) <--------------------------------------------------------
let receipt = storage
.set(U256::from(3))
.send()
.await
.unwrap()
.with_timeout(Some(Duration::from_secs(2)))
.get_receipt()
.await;
assert!(receipt.is_err()); // this should NOT fail <--------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////////////////////////////////

// ISSUE 2. (same but different)
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// re-building an entirely new contract instance also does NOT work <--------------------------------------------------------
let storage = Storage::new(*storage.address(), provider.clone());
assert_eq!(storage.get().call().await.unwrap(), U256::from(1));

let receipt = storage.set(U256::from(30))
.send()
.await
.unwrap()
.with_timeout(Some(Duration::from_secs(2)))
.get_receipt()
.await;
assert!(receipt.is_err()); // this should NOT fail <--------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////////////////////////////////

// stored value is still 1 (original value after reorg)
assert_eq!(storage.get().call().await.unwrap(), U256::from(1));

// if we manually construct the transaction, it will work
let tx = storage
.set(U256::from(10))
.nonce(
provider
.get_transaction_count(signer.address())
.await
.unwrap(),
)
.gas(10_000_000)
.max_fee_per_gas(10_000_000)
.max_priority_fee_per_gas(1)
.chain_id(1234)
.build_raw_transaction(signer)
.await
.unwrap();
let receipt = provider
.send_raw_transaction(&tx)
.await
.unwrap()
.get_receipt()
.await
.unwrap();
assert!(receipt.status()); // note that this transaction passed

assert_eq!(storage.get().call().await.unwrap(), U256::from(30));
//^--------- the last transaction sent from the contract instance (which had a nonce too high)
// was still lingering in the mempool and get's applied now..
}

// simple storage contract for testing
sol! {
#[sol(rpc, bytecode = "6080604052348015600e575f80fd5b5060405161020f38038061020f8339818101604052810190602e9190606b565b805f81905550506091565b5f80fd5b5f819050919050565b604d81603d565b81146056575f80fd5b50565b5f815190506065816046565b92915050565b5f60208284031215607d57607c6039565b5b5f6088848285016059565b91505092915050565b6101718061009e5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c80633fa4f2451461004357806360fe47b1146100615780636d4ce63c1461007d575b5f80fd5b61004b61009b565b60405161005891906100c9565b60405180910390f35b61007b60048036038101906100769190610110565b6100a0565b005b6100856100a9565b60405161009291906100c9565b60405180910390f35b5f5481565b805f8190555050565b5f8054905090565b5f819050919050565b6100c3816100b1565b82525050565b5f6020820190506100dc5f8301846100ba565b92915050565b5f80fd5b6100ef816100b1565b81146100f9575f80fd5b50565b5f8135905061010a816100e6565b92915050565b5f60208284031215610125576101246100e2565b5b5f610132848285016100fc565b9150509291505056fea26469706673582212203294a1485ad6035630092feab3c12235dae68a8920350d2678d9097f36cce44364736f6c634300081a0033")]
contract Storage {
uint256 public value;

constructor(uint256 _value) {
value = _value;
}

function set(uint256 _value) public {
value = _value;
}

function get() public view returns (uint256) {
return value;
}
}
}
```

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。