FuelLabs / fuels-rs

Fuel Network Rust SDK
https://fuellabs.github.io/fuels-rs
Apache License 2.0
44.06k stars 1.34k forks source link

Impersonate Account Asset Transfer Fails: InvalidSignature #1507

Open varun-doshi opened 1 month ago

varun-doshi commented 1 month ago

I'm trying to test the ImpersonateAccount feature which allows taking control of any account without its private key and run transactions on my fork.

However, it throws an error InvalidSignature when trying to transfer assets to another account. The exact error is as follows:

Decode error: Custom { kind: Other, error: \"Response errors; Invalid transaction data: Validity(InputInvalidSignature { index: 0 })\" }")

I've tried this with two setups: Setup 1:

Setup 2:

The conclusion I come to is that it looks like some Signature verification fails when using the impersonated wallet.I may be wrong though.

Steps to reproduce

use std::{ops::Add, str::FromStr};
use dotenv::dotenv;

use fuels::{accounts::{impersonated_account, provider, wallet::Wallet}, client::PaginationRequest, crypto::SecretKey, prelude::*, types::{errors::transaction, ContractId, Identity}};

#[tokio::test]
async fn transfer_testnet_funds_from_impersonate(){

    let provider=Provider::connect("https://testnet.fuel.network/").await.unwrap();
    println!("Block before:{:?}",provider.latest_block_height().await.unwrap());

    let BASE_ASSET_ID=AssetId::from_str("0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07").unwrap();

    let my_address=Address::from_str("0xcf3600a55aa1974397559aebb24ddd6c8e9067c1a02a21c2531b8eac2bc7b7c7").unwrap();
    let bech_my_address=Bech32Address::from(my_address);

let receiver_address=Bech32Address::from_str("fuel18llu9z7mpfrqyclwm2d4d7w9z47gqjxztmg3dsayuh8w0zajfwus34ml3a").unwrap();
    let imper=ImpersonatedAccount::new(bech_my_address, Some(provider.clone()));
    let amt:u64=1;
    let mut tx_policies=TxPolicies::default().with_max_fee(900);

    println!("Balance of testnet account:{:?}",imper.get_spendable_resources(BASE_ASSET_ID, 1, None).await.unwrap());
    println!("Balance of base asset:{:?}",imper.get_asset_balance(&BASE_ASSET_ID).await.unwrap());

    //FAILS
    let transfer=imper.transfer(&receiver_address, amt,BASE_ASSET_ID , tx_policies).await.unwrap();
    println!("RECEIPT OF IMPERSONATE TRANSFER:{:?}",transfer.1[0]);

}

#[tokio::test]
async fn transfer_from_personal(){
    let provider=Provider::connect("https://testnet.fuel.network/").await.unwrap();
    println!("Block before:{:?}",provider.latest_block_height().await.unwrap());

    let BASE_ASSET_ID=AssetId::from_str("0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07").unwrap();
    let OTHER_ASSET_ID=AssetId::from_str("0x2a0d0ed9d2217ec7f32dcd9a1902ce2a66d68437aeff84e3a3cc8bebee0d2eea").unwrap();

    // fuel1eumqpf265xt58964nt4mynwadj8fqe7p5q4zrsjnrw82c278klrs53n24w
    dotenv().ok();
    let secret = match std::env::var("SECRET") {
        Ok(s) => s,
        Err(error) => panic!("❌ Cannot find .env file: {:#?}", error),
    };
    let secret_key=SecretKey::from_str(&secret).unwrap();
    let my_priv_acc=WalletUnlocked::new_from_private_key(secret_key, Some(provider.clone()));

    let receiver_address=Bech32Address::from_str("fuel18llu9z7mpfrqyclwm2d4d7w9z47gqjxztmg3dsayuh8w0zajfwus34ml3a").unwrap();
    let amt:u64=1;
    let mut tx_policies=TxPolicies::default().with_max_fee(900);

    println!("My balance:{:?}",provider.get_balances(&my_priv_acc.address()).await.unwrap());
    println!("Spendable resource:{:?}",my_priv_acc.get_spendable_resources(BASE_ASSET_ID, 1, None).await.unwrap());

    //PASSES
    let transfer=my_priv_acc.transfer(&receiver_address, amt,BASE_ASSET_ID , tx_policies).await.unwrap();
    println!("RECEIPT FROM TESTNET TRANSFER: {:?}",transfer.1[0]);
}

My current fuel setup:

Installed toolchains
--------------------
latest-aarch64-apple-darwin (default)

active toolchain
----------------
latest-aarch64-apple-darwin (default)
  forc : 0.63.1
    - forc-client
      - forc-deploy : 0.63.1
      - forc-run : 0.63.1
    - forc-crypto : 0.63.1
    - forc-debug : 0.63.1
    - forc-doc : 0.63.1
    - forc-fmt : 0.63.1
    - forc-lsp : 0.63.1
    - forc-tx : 0.63.1
    - forc-wallet : 0.9.0
  fuel-core : 0.34.0
  fuel-core-keygen : 0.34.0

fuels versions
--------------
forc : 0.66.1
forc-wallet : 0.66.0

My Cargo.toml:

[package]
name = "test-v2"
description = "A cargo-generate template for Rust + Sway integration testing."
version = "0.1.0"
edition = "2021"
authors = ["Varun Doshi <doshivarun202@gmail.com>"]
license = "Apache-2.0"

[dev-dependencies]
fuels = { version = "0.66.0", features = ["fuel-core-lib"] }
tokio = { version = "1.12", features = ["rt", "macros"] }

[[test]]
harness = true
name = "integration_tests"
path = "tests/harness.rs"

[dependencies]
dotenv = "0.15.0"
varun-doshi commented 1 month ago

I understand there is a method:

let node_config = NodeConfig {
        utxo_validation: false,
        ..Default::default()
    };

But from what I understand, this can only be set for a local node What if I want to connect to a remote RPC. Can I set the NodeConfig for that provider? Currently, I do not see any functions to carry this out.

I would also like to know if it is possible to fork the testnet from a historic block. So connect to a remote RPC but at Block:900 (lets assume the current testnet block is 1000).

hal3e commented 1 month ago

@varun-doshi your observation is correct. You can not use the ImpersonateAccount without setting the utxo_validation to false. There is no way of setting this config to an already running node. That would be a huge vulnerability. So if you setup your local node and set the validation to false you can use the impersonated accounts.

I have talked with the client team and currently there is no easy way to fork the tesnet. The only way, for now, is to sync the whole testnet (can take a lot of space) and then rewind to a specific height.