Skip to content

Commit

Permalink
Move spoof logic to separate module
Browse files Browse the repository at this point in the history
  • Loading branch information
m30m committed Feb 5, 2024
1 parent 67536bc commit 8edea62
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 133 deletions.
137 changes: 4 additions & 133 deletions auction-server/src/liquidation_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use {
UnixTimestamp,
VerifiedLiquidationOpportunity,
},
token_spoof,
},
anyhow::{
anyhow,
Expand Down Expand Up @@ -83,139 +84,9 @@ impl From<(Address, U256)> for TokenQty {
}
}

/// Calculate the storage key for the balance of an address in an ERC20 token. This is used to spoof the balance
///
/// # Arguments
///
/// * `owner`: The address of the owner of the balance
/// * `balance_slot`: The slot where the balance mapping is located inside the contract storage
///
/// returns: H256
fn calculate_balance_storage_key(owner: Address, balance_slot: U256) -> H256 {
let mut buffer: [u8; 64] = [0; 64];
buffer[12..32].copy_from_slice(&owner.as_bytes());
balance_slot.to_big_endian(buffer[32..64].as_mut());
keccak256(Bytes::from(buffer)).into()
}


/// Calculate the storage key for the allowance of an spender for an address in an ERC20 token.
/// This is used to spoof the allowance
///
/// # Arguments
///
/// * `owner`: The address of the owner where the allowance is calculated
/// * `spender`: The address of the spender where the allowance is calculated
/// * `allowance_slot`: The slot where the allowance mapping is located inside the contract storage
///
/// returns: H256
fn calculate_allowance_storage_key(owner: Address, spender: Address, allowance_slot: U256) -> H256 {
let mut buffer_spender: [u8; 64] = [0; 64];
buffer_spender[12..32].copy_from_slice(&owner.as_bytes());
allowance_slot.to_big_endian(buffer_spender[32..64].as_mut());
let spender_slot = keccak256(Bytes::from(buffer_spender));

let mut buffer_allowance: [u8; 64] = [0; 64];
buffer_allowance[12..32].copy_from_slice(&spender.as_bytes());
buffer_allowance[32..64].copy_from_slice(&spender_slot);
keccak256(Bytes::from(buffer_allowance)).into()
}


/// Find the balance slot of an ERC20 token that can be used to spoof the balance of an address
/// Returns an error if no slot is found or if the network calls fail
///
/// # Arguments
///
/// * `token`: ERC20 token address
/// * `client`: Client to interact with the blockchain
///
/// returns: Result<U256, Error>
async fn find_spoof_balance_slot(token: Address, client: Arc<Provider<Http>>) -> Result<U256> {
let contract = ERC20::new(token, client.clone());
let fake_owner = LocalWallet::new(&mut rand::thread_rng());
for balance_slot in 0..32 {
let tx = contract.balance_of(fake_owner.address()).tx;
let mut state = spoof::State::default();
let balance_storage_key =
calculate_balance_storage_key(fake_owner.address(), balance_slot.into());
let value: [u8; 32] = rand::random();
state
.account(token)
.store(balance_storage_key, value.into());
let result = client.call_raw(&tx).state(&state).await?;
if result == Bytes::from(value) {
return Ok(balance_slot.into());
}
}
Err(anyhow!("Could not find balance slot"))
}


/// Find the allowance slot of an ERC20 token that can be used to spoof the allowance of an address
/// Returns an error if no slot is found or if the network calls fail
///
/// # Arguments
///
/// * `token`: ERC20 token address
/// * `client`: Client to interact with the blockchain
///
/// returns: Result<U256, Error>
async fn find_spoof_allowance_slot(token: Address, client: Arc<Provider<Http>>) -> Result<U256> {
let contract = ERC20::new(token, client.clone());
let fake_owner = LocalWallet::new(&mut rand::thread_rng());
let fake_spender = LocalWallet::new(&mut rand::thread_rng());

for allowance_slot in 0..32 {
let tx = contract
.allowance(fake_owner.address(), fake_spender.address())
.tx;
let mut state = spoof::State::default();
let balance_storage_key = calculate_allowance_storage_key(
fake_owner.address(),
fake_spender.address(),
allowance_slot.into(),
);
let value: [u8; 32] = rand::random();
state
.account(token)
.store(balance_storage_key, value.into());
let result = client.call_raw(&tx).state(&state).await?;
if result == Bytes::from(value) {
return Ok(allowance_slot.into());
}
}
Err(anyhow!("Could not find allowance slot"))
}

/// Find the spoof info for an ERC20 token. This includes the balance slot and the allowance slot
/// Returns an error if no balance or allowance slot is found
/// # Arguments
///
/// * `token`: ERC20 token address
/// * `client`: Client to interact with the blockchain
///
/// returns: Result<SpoofInfo>
async fn find_spoof_info(token: Address, client: Arc<Provider<Http>>) -> Result<SpoofInfo> {
let balance_slot = find_spoof_balance_slot(token, client.clone()).await?;
let allowance_slot = find_spoof_allowance_slot(token, client.clone()).await?;
Ok(SpoofInfo::Spoofed {
balance_slot,
allowance_slot,
})
}

/// Verify an opportunity by simulating the liquidation call and checking the result
/// Simulation is done by spoofing the balances and allowances of a random liquidator
/// Returns Ok(()) if the simulation is successful or if the tokens cannot be spoofed
///
/// # Arguments
///
/// * `opportunity`:
/// * `chain_store`:
/// * `per_operator`:
///
/// returns: Result<()>
pub async fn verify_opportunity(
opportunity: VerifiedLiquidationOpportunity,
chain_store: &ChainStore,
Expand Down Expand Up @@ -266,7 +137,7 @@ pub async fn verify_opportunity(
let spoof_info = match token_spoof_info.get(&token) {
Some(info) => info.clone(),
None => {
let result = find_spoof_info(token, client.clone())
let result = token_spoof::find_spoof_info(token, client.clone())
.await
.unwrap_or_else(|e| {
tracing::error!("Error finding spoof info: {:?}", e);
Expand All @@ -288,13 +159,13 @@ pub async fn verify_opportunity(
allowance_slot,
} => {
let balance_storage_key =
calculate_balance_storage_key(fake_wallet.address(), balance_slot);
token_spoof::calculate_balance_storage_key(fake_wallet.address(), balance_slot);
let value: [u8; 32] = amount.into();
state
.account(token)
.store(balance_storage_key, value.into());

let allowance_storage_key = calculate_allowance_storage_key(
let allowance_storage_key = token_spoof::calculate_allowance_storage_key(
fake_wallet.address(),
chain_store.config.adapter_contract,
allowance_slot,
Expand Down
1 change: 1 addition & 0 deletions auction-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod config;
mod liquidation_adapter;
mod serde;
mod state;
mod token_spoof;

#[tokio::main]
async fn main() -> Result<()> {
Expand Down
156 changes: 156 additions & 0 deletions auction-server/src/token_spoof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/// This module contains functions to spoof the balance and allowance of an address in an
/// ERC20 token.
/// This is used to alter the balance and allowance of an address in a token to verify
/// the liquidation opportunities in a simulation environment.
/// The spoofing is done by finding the storage slot of the balance and allowance of an address
/// This approach is just a heuristic and will not work for all tokens, specially if the token
/// has a custom storage layout or logic to calculate the balance or allowance
use ethers::addressbook::Address;
use {
crate::{
liquidation_adapter::ERC20,
state::SpoofInfo,
},
anyhow::anyhow,
ethers::{
core::rand,
prelude::{
spoof,
Bytes,
LocalWallet,
Provider,
RawCall,
Signer,
H256,
U256,
},
providers::Http,
utils::keccak256,
},
std::sync::Arc,
};

/// Calculate the storage key for the balance of an address in an ERC20 token. This is used to spoof the balance
///
/// # Arguments
///
/// * `owner`: The address of the owner of the balance
/// * `balance_slot`: The slot where the balance mapping is located inside the contract storage
pub fn calculate_balance_storage_key(owner: Address, balance_slot: U256) -> H256 {
let mut buffer: [u8; 64] = [0; 64];
buffer[12..32].copy_from_slice(&owner.as_bytes());
balance_slot.to_big_endian(buffer[32..64].as_mut());
keccak256(Bytes::from(buffer)).into()
}


/// Calculate the storage key for the allowance of an spender for an address in an ERC20 token.
/// This is used to spoof the allowance
///
/// # Arguments
///
/// * `owner`: The address of the owner where the allowance is calculated
/// * `spender`: The address of the spender where the allowance is calculated
/// * `allowance_slot`: The slot where the allowance mapping is located inside the contract storage
pub fn calculate_allowance_storage_key(
owner: Address,
spender: Address,
allowance_slot: U256,
) -> H256 {
let mut buffer_spender: [u8; 64] = [0; 64];
buffer_spender[12..32].copy_from_slice(&owner.as_bytes());
allowance_slot.to_big_endian(buffer_spender[32..64].as_mut());
let spender_slot = keccak256(Bytes::from(buffer_spender));

let mut buffer_allowance: [u8; 64] = [0; 64];
buffer_allowance[12..32].copy_from_slice(&spender.as_bytes());
buffer_allowance[32..64].copy_from_slice(&spender_slot);
keccak256(Bytes::from(buffer_allowance)).into()
}


/// Find the balance slot of an ERC20 token that can be used to spoof the balance of an address
/// Returns an error if no slot is found or if the network calls fail
///
/// # Arguments
///
/// * `token`: ERC20 token address
/// * `client`: Client to interact with the blockchain
async fn find_spoof_balance_slot(
token: Address,
client: Arc<Provider<Http>>,
) -> anyhow::Result<U256> {
let contract = ERC20::new(token, client.clone());
let fake_owner = LocalWallet::new(&mut rand::thread_rng());
for balance_slot in 0..32 {
let tx = contract.balance_of(fake_owner.address()).tx;
let mut state = spoof::State::default();
let balance_storage_key =
calculate_balance_storage_key(fake_owner.address(), balance_slot.into());
let value: [u8; 32] = rand::random();
state
.account(token)
.store(balance_storage_key, value.into());
let result = client.call_raw(&tx).state(&state).await?;
if result == Bytes::from(value) {
return Ok(balance_slot.into());
}
}
Err(anyhow!("Could not find balance slot"))
}


/// Find the allowance slot of an ERC20 token that can be used to spoof the allowance of an address
/// Returns an error if no slot is found or if the network calls fail
///
/// # Arguments
///
/// * `token`: ERC20 token address
/// * `client`: Client to interact with the blockchain
async fn find_spoof_allowance_slot(
token: Address,
client: Arc<Provider<Http>>,
) -> anyhow::Result<U256> {
let contract = ERC20::new(token, client.clone());
let fake_owner = LocalWallet::new(&mut rand::thread_rng());
let fake_spender = LocalWallet::new(&mut rand::thread_rng());

for allowance_slot in 0..32 {
let tx = contract
.allowance(fake_owner.address(), fake_spender.address())
.tx;
let mut state = spoof::State::default();
let balance_storage_key = calculate_allowance_storage_key(
fake_owner.address(),
fake_spender.address(),
allowance_slot.into(),
);
let value: [u8; 32] = rand::random();
state
.account(token)
.store(balance_storage_key, value.into());
let result = client.call_raw(&tx).state(&state).await?;
if result == Bytes::from(value) {
return Ok(allowance_slot.into());
}
}
Err(anyhow!("Could not find allowance slot"))
}

/// Find the spoof info for an ERC20 token. This includes the balance slot and the allowance slot
/// Returns an error if no balance or allowance slot is found
/// # Arguments
///
/// * `token`: ERC20 token address
/// * `client`: Client to interact with the blockchain
pub async fn find_spoof_info(
token: Address,
client: Arc<Provider<Http>>,
) -> anyhow::Result<SpoofInfo> {
let balance_slot = find_spoof_balance_slot(token, client.clone()).await?;
let allowance_slot = find_spoof_allowance_slot(token, client.clone()).await?;
Ok(SpoofInfo::Spoofed {
balance_slot,
allowance_slot,
})
}

0 comments on commit 8edea62

Please sign in to comment.