78 lines
2.8 KiB
Rust
78 lines
2.8 KiB
Rust
use ethers::{
|
|
contract::abigen,
|
|
core::{
|
|
types::{Address, TransactionRequest, H256},
|
|
utils::{parse_ether, Geth},
|
|
},
|
|
providers::{
|
|
call_raw::{self, RawCall},
|
|
Http, Provider,
|
|
},
|
|
};
|
|
use eyre::Result;
|
|
use std::sync::Arc;
|
|
|
|
abigen!(Greeter, "ethers-contract/tests/solidity-contracts/greeter.json",);
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let geth = Geth::new().spawn();
|
|
let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
|
|
let client = Arc::new(provider);
|
|
|
|
// Both empty accounts
|
|
let target: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
|
|
let from: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
|
|
|
|
// Override the sender's balance for the call
|
|
let pay_amt = parse_ether(1u64)?;
|
|
let tx = TransactionRequest::pay(target, pay_amt).from(from);
|
|
let state = call_raw::balance(from, pay_amt * 2);
|
|
|
|
// The call succeeds as if the sender had sufficient balance
|
|
client.call_raw(&tx.into()).state(&state).await.expect("balance override");
|
|
|
|
// Get the runtime bytecode for the Greeter contract using eth_call to
|
|
// simulate the deploy transaction
|
|
let deploy = Greeter::deploy(client.clone(), "Hi".to_string())?;
|
|
let runtime_bytecode = deploy.call_raw().await?;
|
|
|
|
// Instantiate a Greeter, though no bytecode exists at the target address
|
|
let greeter = Greeter::new(target, client.clone());
|
|
|
|
// Override the target account's code, simulating a call to Greeter.greet()
|
|
// as if the Greeter contract was deployed at the target address
|
|
let state = call_raw::code(target, runtime_bytecode.clone());
|
|
let res = greeter.greet().call_raw().state(&state).await?;
|
|
|
|
// greet() returns the empty string, because the target account's storage is empty
|
|
assert_eq!(&res, "");
|
|
|
|
// Encode the greeting string as solidity expects it to be stored
|
|
let greeting = "Hello, world";
|
|
let greet_slot = H256::zero();
|
|
let greet_val = encode_string_for_storage(greeting);
|
|
|
|
// Override the target account's code and storage
|
|
let mut state = call_raw::state();
|
|
state.account(target).code(runtime_bytecode.clone()).store(greet_slot, greet_val);
|
|
let res = greeter.greet().call_raw().state(&state).await?;
|
|
|
|
// The call returns "Hello, world"
|
|
assert_eq!(&res, greeting);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Solidity stores strings shorter than 32 bytes in a single storage slot,
|
|
// with the lowest order byte storing 2 * length and the higher order
|
|
// bytes storing the string data (left aligned)
|
|
fn encode_string_for_storage(s: &str) -> H256 {
|
|
let mut bytes = s.as_bytes().to_vec();
|
|
let len = bytes.len();
|
|
assert!(len < 32, "longer strings aren't stored in a single slot");
|
|
bytes.resize(31, 0);
|
|
bytes.push(len as u8 * 2);
|
|
H256::from_slice(&bytes)
|
|
}
|