Files
serai/processor/ethereum/test-primitives/src/lib.rs
2025-11-04 10:20:17 -05:00

114 lines
3.6 KiB
Rust

#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
use k256::{elliptic_curve::sec1::ToEncodedPoint, ProjectivePoint};
use alloy_core::{
primitives::{Address, U256, Bytes, Signature, TxKind},
hex::FromHex,
};
use alloy_consensus::{SignableTransaction, TxLegacy, Signed};
use alloy_rpc_types_eth::TransactionReceipt;
use alloy_provider::{Provider, RootProvider};
use ethereum_primitives::{keccak256, deterministically_sign};
fn address(point: &ProjectivePoint) -> [u8; 20] {
let encoded_point = point.to_encoded_point(false);
// Last 20 bytes of the hash of the concatenated x and y coordinates
// We obtain the concatenated x and y coordinates via the uncompressed encoding of the point
keccak256(&encoded_point.as_ref()[1 .. 65])[12 ..].try_into().unwrap()
}
/// Fund an account.
pub async fn fund_account(provider: &RootProvider, address: Address, value: U256) {
let _: () = provider
.raw_request("anvil_setBalance".into(), [address.to_string(), value.to_string()])
.await
.unwrap();
}
/// Publish an already-signed transaction.
pub async fn publish_tx(provider: &RootProvider, tx: Signed<TxLegacy>) -> TransactionReceipt {
// Fund the sender's address
fund_account(
provider,
tx.recover_signer().unwrap(),
(U256::from(tx.tx().gas_limit) * U256::from(tx.tx().gas_price)) + tx.tx().value,
)
.await;
let (tx, sig, _) = tx.into_parts();
let mut bytes = vec![];
tx.into_signed(sig).eip2718_encode(&mut bytes);
let pending_tx = provider.send_raw_transaction(&bytes).await.unwrap();
pending_tx.get_receipt().await.unwrap()
}
/// Deploy a contract.
///
/// The contract deployment will be done by a random account.
pub async fn deploy_contract(
provider: &RootProvider,
file_path: &str,
constructor_arguments: &[u8],
) -> Address {
let hex_bin_buf = std::fs::read_to_string(file_path).unwrap();
let hex_bin =
if let Some(stripped) = hex_bin_buf.strip_prefix("0x") { stripped } else { &hex_bin_buf };
let mut bin = Vec::<u8>::from(Bytes::from_hex(hex_bin).unwrap());
bin.extend(constructor_arguments);
let deployment_tx = TxLegacy {
chain_id: None,
nonce: 0,
// 100 gwei
gas_price: 100_000_000_000u128,
gas_limit: 1_000_000,
to: TxKind::Create,
value: U256::ZERO,
input: bin.into(),
};
let deployment_tx = deterministically_sign(deployment_tx);
let receipt = publish_tx(provider, deployment_tx).await;
assert!(receipt.status());
receipt.contract_address.unwrap()
}
/// Sign and send a transaction from the specified wallet.
///
/// This assumes the wallet is funded.
pub async fn send(
provider: &RootProvider,
wallet: &k256::ecdsa::SigningKey,
mut tx: TxLegacy,
) -> TransactionReceipt {
let verifying_key = *wallet.verifying_key().as_affine();
let address = Address::from(address(&verifying_key.into()));
// https://github.com/alloy-rs/alloy/issues/539
// let chain_id = provider.get_chain_id().await.unwrap();
// tx.chain_id = Some(chain_id);
tx.chain_id = None;
tx.nonce = provider.get_transaction_count(address).await.unwrap();
// 100 gwei
tx.gas_price = 100_000_000_000u128;
let sig = wallet.sign_prehash_recoverable(tx.signature_hash().as_ref()).unwrap();
assert_eq!(address, tx.clone().into_signed(sig.into()).recover_signer().unwrap());
assert!(
provider.get_balance(address).await.unwrap() >
((U256::from(tx.gas_price) * U256::from(tx.gas_limit)) + tx.value)
);
let mut bytes = vec![];
tx.into_signed(Signature::from(sig)).eip2718_encode(&mut bytes);
let pending_tx = provider.send_raw_transaction(&bytes).await.unwrap();
pending_tx.get_receipt().await.unwrap()
}