Account Impersonation
Overview
Account impersonation allows you to send transactions from any address without possessing its private key. This is invaluable for testing scenarios involving:
- Whale accounts: Test large transfers without controlling the actual whale's key
- DAO contracts: Simulate governance actions from multisig or timelock contracts
- Protocol admins: Test admin-only functions from privileged addresses
- External users: Simulate realistic user interactions in integration tests
Using impersonate() with try-with-resources
The recommended way to use impersonation is with Java's try-with-resources pattern. This ensures the impersonation is automatically stopped when the session ends, preventing resource leaks.
import sh.brane.rpc.Brane;
import sh.brane.rpc.ImpersonationSession;
import sh.brane.core.types.Address;
import sh.brane.core.types.Wei;
import sh.brane.core.model.TransactionRequest;
Brane.Tester tester = Brane.connectTest();
// Address to impersonate (no private key needed)
Address whale = new Address("0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8");
// Fund the address first (impersonation doesn't create ETH)
tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("10000")));
// Impersonate with automatic cleanup
try (ImpersonationSession session = tester.impersonate(whale)) {
var request = TxBuilder.eip1559()
.to(recipient)
.value(Wei.fromEther(new java.math.BigDecimal("1000")))
.build();
// Transaction is sent from the whale address
TransactionReceipt receipt = session.sendTransactionAndWait(request);
System.out.println("Transfer confirmed: " + receipt.transactionHash());
}
// Impersonation automatically stopped when session closesImpersonationSession API
The ImpersonationSession interface provides methods for sending transactions as the impersonated address:
| Method | Description |
|---|---|
address() | Returns the impersonated address |
sendTransaction(request) | Sends a transaction, returns immediately with hash |
sendTransactionAndWait(request) | Sends and waits for confirmation (60s timeout) |
sendTransactionAndWait(request, timeout, poll) | Sends with custom timeout settings |
close() | Stops impersonation (called automatically with try-with-resources) |
How ImpersonationSession Works
When you call tester.impersonate(address):
- Brane calls the test node's impersonation RPC method (e.g.,
anvil_impersonateAccount) - The test node allows transactions from that address without signature verification
- An
ImpersonationSessionis returned that wraps the impersonated address - Transactions sent via the session use
eth_sendTransactionwithfromset to the impersonated address - When the session closes, Brane calls the stop impersonation RPC (e.g.,
anvil_stopImpersonatingAccount)
// The session automatically sets the 'from' field
try (ImpersonationSession session = tester.impersonate(whale)) {
// This request doesn't need to specify 'from'
var request = TxBuilder.eip1559()
.to(recipient)
.value(Wei.fromEther(new java.math.BigDecimal("100")))
.data(calldata)
.build();
// Session adds from=whale automatically
Hash txHash = session.sendTransaction(request);
}Whale Testing Pattern
A common use case is testing interactions with whale accounts - addresses holding large token balances on mainnet. Here's a complete pattern:
import sh.brane.rpc.Brane;
import sh.brane.rpc.ImpersonationSession;
import sh.brane.rpc.SnapshotId;
import sh.brane.core.types.Address;
import sh.brane.core.types.Wei;
public class WhaleTestExample {
// Known whale addresses (example: Binance hot wallet)
private static final Address BINANCE_WHALE =
new Address("0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8");
public void testLargeTransfer(Brane.Tester tester) {
// 1. Take snapshot for test isolation
SnapshotId snapshot = tester.snapshot();
try {
Address recipient = new Address("0x...");
// 2. Fund the whale with ETH for gas
tester.setBalance(BINANCE_WHALE, Wei.fromEther(new java.math.BigDecimal("100")));
// 3. Impersonate and execute
try (ImpersonationSession session = tester.impersonate(BINANCE_WHALE)) {
var request = TxBuilder.eip1559()
.to(recipient)
.value(Wei.fromEther(new java.math.BigDecimal("50")))
.build();
TransactionReceipt receipt = session.sendTransactionAndWait(request);
// 4. Verify the transfer
BigInteger recipientBalance = tester.getBalance(recipient);
assert recipientBalance.compareTo(Wei.fromEther(new java.math.BigDecimal("50")).value()) >= 0;
}
} finally {
// 5. Restore state for next test
tester.revert(snapshot);
}
}
}Forking with Real Whale Balances
For more realistic testing, fork from mainnet to use actual whale balances:
// Fork mainnet at a specific block
tester.reset("https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY", 18_000_000L);
// Now impersonate a real whale with their actual balance
Address usdcWhale = new Address("0x...");
try (ImpersonationSession session = tester.impersonate(usdcWhale)) {
// Whale already has tokens - no need to set balance
// Execute ERC20 transfer...
}sendImpersonatedTransaction
For simple one-off transactions, you can manually manage impersonation without the session pattern. However, the try-with-resources pattern is strongly recommended.
Manual Impersonation (Not Recommended)
Address target = new Address("0x...");
// Start impersonation
ImpersonationSession session = tester.impersonate(target);
try {
// Send transaction
var request = TxBuilder.eip1559()
.to(recipient)
.value(Wei.fromEther(new java.math.BigDecimal("1")))
.build();
Hash txHash = session.sendTransaction(request);
// Wait for confirmation
TransactionReceipt receipt = tester.waitForReceipt(txHash, 60_000, 1_000);
} finally {
// Must manually close!
session.close();
}stopImpersonating
You can also stop impersonation directly via the tester:
Address target = new Address("0x...");
// Start impersonation (without session)
tester.impersonate(target);
// ... do work ...
// Stop impersonation explicitly
tester.stopImpersonating(target);Auto-Impersonation (Anvil Only)
Anvil supports automatic impersonation where any address can send transactions without explicit impersonation calls. This is useful for rapid prototyping but should be used carefully.
// Enable auto-impersonate (Anvil only)
tester.enableAutoImpersonate();
// Now any address can send transactions via eth_sendTransaction
// without calling impersonate() first
// Disable when done
tester.disableAutoImpersonate();Test Node Compatibility
Impersonation works across all supported test nodes with different RPC method prefixes:
| Operation | Anvil | Hardhat | Ganache |
|---|---|---|---|
| Start impersonation | anvil_impersonateAccount | hardhat_impersonateAccount | evm_impersonateAccount |
| Stop impersonation | anvil_stopImpersonatingAccount | hardhat_stopImpersonatingAccount | evm_stopImpersonatingAccount |
| Auto-impersonate | anvil_autoImpersonateAccount | Not supported | Not supported |
The correct RPC method is selected automatically based on TestNodeMode.
Common Patterns
Testing Contract Admin Functions
// Impersonate the contract owner
Address owner = new Address("0x..."); // Read from contract
try (ImpersonationSession session = tester.impersonate(owner)) {
// Call admin-only function
HexData calldata = abi.encode("setFee", newFee);
var request = TxBuilder.eip1559()
.to(contractAddress)
.data(calldata)
.build();
session.sendTransactionAndWait(request);
}Testing DAO Governance
// Impersonate a timelock contract
Address timelock = new Address("0x...");
tester.setBalance(timelock, Wei.fromEther(new java.math.BigDecimal("1"))); // For gas
try (ImpersonationSession session = tester.impersonate(timelock)) {
// Execute queued governance proposal
HexData executeCalldata = abi.encode("execute", proposalId);
var request = TxBuilder.eip1559()
.to(governorAddress)
.data(executeCalldata)
.build();
session.sendTransactionAndWait(request);
}Multiple Impersonations
You can have multiple impersonation sessions, but only one per address:
Address alice = new Address("0xAlice...");
Address bob = new Address("0xBob...");
tester.setBalance(alice, Wei.fromEther(new java.math.BigDecimal("10")));
tester.setBalance(bob, Wei.fromEther(new java.math.BigDecimal("10")));
// Sequential impersonations
try (ImpersonationSession aliceSession = tester.impersonate(alice)) {
aliceSession.sendTransactionAndWait(aliceRequest);
}
try (ImpersonationSession bobSession = tester.impersonate(bob)) {
bobSession.sendTransactionAndWait(bobRequest);
}Error Handling
RpcException on Impersonation Failure
try (ImpersonationSession session = tester.impersonate(address)) {
session.sendTransactionAndWait(request);
} catch (RpcException e) {
// Handle RPC errors (network issues, invalid params)
System.err.println("RPC error: " + e.getMessage());
} catch (RevertException e) {
// Handle transaction reverts
System.err.println("Transaction reverted: " + e.getMessage());
}Session Already Closed
ImpersonationSession session = tester.impersonate(address);
session.close();
// This throws IllegalStateException
session.sendTransaction(request); // ERROR: ImpersonationSession has been closedBest Practices
Always Use try-with-resources
// Good - automatic cleanup
try (ImpersonationSession session = tester.impersonate(address)) {
session.sendTransactionAndWait(request);
}
// Bad - manual cleanup prone to leaks
ImpersonationSession session = tester.impersonate(address);
session.sendTransactionAndWait(request);
session.close(); // Easy to forget, especially with exceptionsFund Addresses Before Impersonating
// Good - fund first
tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("100")));
try (ImpersonationSession session = tester.impersonate(whale)) {
session.sendTransactionAndWait(request);
}
// Bad - transaction may fail with insufficient funds
try (ImpersonationSession session = tester.impersonate(whale)) {
session.sendTransactionAndWait(request); // May fail!
}Combine with Snapshots for Isolation
SnapshotId snapshot = tester.snapshot();
try {
tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("1000")));
try (ImpersonationSession session = tester.impersonate(whale)) {
session.sendTransactionAndWait(request);
}
// Assert results...
} finally {
tester.revert(snapshot); // Clean state for next test
}Document the Address Being Impersonated
// Good - clear context
// Impersonate Uniswap V3 Router for swap testing
Address uniswapRouter = new Address("0xE592427A0AEce92De3Edee1F18E0157C05861564");
try (ImpersonationSession session = tester.impersonate(uniswapRouter)) {
// ...
}