Skip to content

Commit

Permalink
Deduct EVM fee (#2039)
Browse files Browse the repository at this point in the history
* Deduct fee from sender

* Prevalidate balance for MIN_GAS_PER_TX

* Additional check to prevalidate tx and tests
  • Loading branch information
Jouzo authored Jun 9, 2023
1 parent 96e3f5c commit 174e0ab
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 54 deletions.
33 changes: 31 additions & 2 deletions lib/ain-evm/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,37 @@ impl EVMBackend {
..self.vicinity
};
}

pub fn deduct_prepay_gas(&mut self, sender: H160, prepay_gas: U256) {
trace!(target: "backend", "[deduct_prepay_gas] Deducting {:#x} from {:#x}", prepay_gas, sender);

let basic = self.basic(sender);
let balance = basic.balance.saturating_sub(prepay_gas);
let new_basic = Basic { balance, ..basic };
self.apply(sender, new_basic, None, Vec::new(), false)
.expect("Error deducting account balance");
}

pub fn refund_unused_gas(
&mut self,
sender: H160,
gas_limit: U256,
used_gas: U256,
gas_price: U256,
) {
let refund_gas = gas_limit.saturating_sub(used_gas);
let refund_amount = refund_gas.saturating_mul(gas_price);

trace!(target: "backend", "[refund_unused_gas] Refunding {:#x} to {:#x}", refund_amount, sender);

let basic = self.basic(sender);
let new_basic = Basic {
balance: basic.balance.saturating_add(refund_amount),
..basic
};
self.apply(sender, new_basic, None, Vec::new(), false)
.expect("Error refunding account balance");
}
}

impl EVMBackend {
Expand Down Expand Up @@ -284,7 +315,6 @@ impl BridgeBackend for EVMBackend {
};

self.apply(address, new_basic, None, Vec::new(), false)?;
self.state.commit();
Ok(())
}

Expand All @@ -306,7 +336,6 @@ impl BridgeBackend for EVMBackend {
};

self.apply(address, new_basic, None, Vec::new(), false)?;
self.state.commit();
Ok(())
}
}
Expand Down
42 changes: 31 additions & 11 deletions lib/ain-evm/src/evm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::backend::{EVMBackend, EVMBackendError, InsufficientBalance, Vicinity};
use crate::executor::TxResponse;
use crate::fee::calculate_prepay_gas;
use crate::storage::traits::{BlockStorage, PersistentState, PersistentStateError};
use crate::storage::Storage;
use crate::transaction::bridge::{BalanceUpdate, BridgeTx};
Expand Down Expand Up @@ -124,17 +125,14 @@ impl EVMHandler {
vicinity,
)
.map_err(|e| anyhow!("------ Could not restore backend {}", e))?;
Ok(AinExecutor::new(&mut backend).call(
ExecutorContext {
caller,
to,
value,
data,
gas_limit,
access_list,
},
false,
))
Ok(AinExecutor::new(&mut backend).call(ExecutorContext {
caller,
to,
value,
data,
gas_limit,
access_list,
}))
}

pub fn validate_raw_tx(&self, tx: &str) -> Result<SignedTx, Box<dyn Error>> {
Expand Down Expand Up @@ -175,6 +173,28 @@ impl EVMHandler {
.into());
}

const MIN_GAS_PER_TX: U256 = U256([21_000, 0, 0, 0]);
let balance = self
.get_balance(signed_tx.sender, block_number)
.map_err(|e| anyhow!("Error getting balance {e}"))?;

debug!("[validate_raw_tx] Accout balance : {:x?}", balance);

let prepay_gas = calculate_prepay_gas(&signed_tx);
if balance < MIN_GAS_PER_TX || balance < prepay_gas {
debug!("[validate_raw_tx] Insufficiant balance to pay fees");
return Err(anyhow!("Insufficiant balance to pay fees").into());
}

let gas_limit = U256::from(signed_tx.gas_limit());

// TODO lift MAX_GAS_PER_BLOCK
const MAX_GAS_PER_BLOCK: U256 = U256([30_000_000, 0, 0, 0]);
println!("MAX_GAS_PER_BLOCK : {:#x}", MAX_GAS_PER_BLOCK);
if gas_limit > MAX_GAS_PER_BLOCK {
return Err(anyhow!("Gas limit higher than MAX_GAS_PER_BLOCK").into());
}

Ok(signed_tx)
}

Expand Down
103 changes: 75 additions & 28 deletions lib/ain-evm/src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
backend::{EVMBackend, EVMBackendError},
evm::EVMHandler,
fee::calculate_prepay_gas,
traits::{BridgeBackend, Executor, ExecutorContext},
transaction::SignedTx,
};
Expand Down Expand Up @@ -42,7 +43,8 @@ impl<'backend> AinExecutor<'backend> {
impl<'backend> Executor for AinExecutor<'backend> {
const CONFIG: Config = Config::shanghai();

fn call(&mut self, ctx: ExecutorContext, apply: bool) -> TxResponse {
/// Read-only call
fn call(&mut self, ctx: ExecutorContext) -> TxResponse {
let metadata = StackSubstateMetadata::new(ctx.gas_limit, &Self::CONFIG);
let state = MemoryStackState::new(metadata, self.backend);
let precompiles = BTreeMap::new(); // TODO Add precompile crate
Expand All @@ -52,6 +54,63 @@ impl<'backend> Executor for AinExecutor<'backend> {
.into_iter()
.map(|x| (x.address, x.storage_keys))
.collect::<Vec<_>>();

let (exit_reason, data) = match ctx.to {
Some(address) => executor.transact_call(
ctx.caller.unwrap_or_default(),
address,
ctx.value,
ctx.data.to_vec(),
ctx.gas_limit,
access_list,
),
None => executor.transact_create(
ctx.caller.unwrap_or_default(),
ctx.value,
ctx.data.to_vec(),
ctx.gas_limit,
access_list,
),
};

TxResponse {
exit_reason,
data,
logs: Vec::new(),
used_gas: executor.used_gas(),
}
}

/// Update state
fn exec(&mut self, signed_tx: &SignedTx) -> (TxResponse, ReceiptV3) {
self.backend.update_vicinity_from_tx(signed_tx);
trace!(
"[Executor] Executing EVM TX with vicinity : {:?}",
self.backend.vicinity
);
let ctx = ExecutorContext {
caller: Some(signed_tx.sender),
to: signed_tx.to(),
value: signed_tx.value(),
data: signed_tx.data(),
gas_limit: signed_tx.gas_limit().as_u64(),
access_list: signed_tx.access_list(),
};

let prepay_gas = calculate_prepay_gas(&signed_tx);

self.backend.deduct_prepay_gas(signed_tx.sender, prepay_gas);

let metadata = StackSubstateMetadata::new(ctx.gas_limit, &Self::CONFIG);
let state = MemoryStackState::new(metadata, self.backend);
let precompiles = BTreeMap::new(); // TODO Add precompile crate
let mut executor = StackExecutor::new_with_precompiles(state, &Self::CONFIG, &precompiles);
let access_list = ctx
.access_list
.into_iter()
.map(|x| (x.address, x.storage_keys))
.collect::<Vec<_>>();

let (exit_reason, data) = match ctx.to {
Some(address) => executor.transact_call(
ctx.caller.unwrap_or_default(),
Expand All @@ -73,10 +132,17 @@ impl<'backend> Executor for AinExecutor<'backend> {
let used_gas = executor.used_gas();
let (values, logs) = executor.into_state().deconstruct();
let logs = logs.into_iter().collect::<Vec<_>>();
if apply && exit_reason.is_succeed() {
if exit_reason.is_succeed() {
ApplyBackend::apply(self.backend, values, logs.clone(), true);
}

self.backend.refund_unused_gas(
signed_tx.sender,
signed_tx.gas_limit(),
U256::from(used_gas),
signed_tx.gas_price(),
);

let receipt = ReceiptV3::EIP1559(EIP658ReceiptData {
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Expand All @@ -88,32 +154,14 @@ impl<'backend> Executor for AinExecutor<'backend> {
used_gas: U256::from(used_gas),
});

TxResponse {
exit_reason,
data,
logs,
used_gas,
receipt,
}
}

fn exec(&mut self, signed_tx: &SignedTx) -> TxResponse {
let apply = true;
self.backend.update_vicinity_from_tx(signed_tx);
trace!(
"[Executor] Executing EVM TX with vicinity : {:?}",
self.backend.vicinity
);
self.call(
ExecutorContext {
caller: Some(signed_tx.sender),
to: signed_tx.to(),
value: signed_tx.value(),
data: signed_tx.data(),
gas_limit: signed_tx.gas_limit().as_u64(),
access_list: signed_tx.access_list(),
(
TxResponse {
exit_reason,
data,
logs,
used_gas,
},
apply,
receipt,
)
}
}
Expand All @@ -124,5 +172,4 @@ pub struct TxResponse {
pub data: Vec<u8>,
pub logs: Vec<Log>,
pub used_gas: u64,
pub receipt: ReceiptV3,
}
11 changes: 11 additions & 0 deletions lib/ain-evm/src/fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use ethereum_types::U256;

use crate::transaction::SignedTx;

pub fn calculate_prepay_gas(signed_tx: &SignedTx) -> U256 {
match &signed_tx.transaction {
ethereum::TransactionV2::Legacy(tx) => tx.gas_limit.saturating_mul(tx.gas_price),
ethereum::TransactionV2::EIP2930(tx) => tx.gas_limit.saturating_mul(tx.gas_price),
ethereum::TransactionV2::EIP1559(tx) => tx.gas_limit.saturating_mul(tx.max_fee_per_gas),
}
}
18 changes: 10 additions & 8 deletions lib/ain-evm/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,15 @@ impl Handlers {
for (queue_tx, hash) in self.evm.tx_queues.get_cloned_vec(context) {
match queue_tx {
QueueTx::SignedTx(signed_tx) => {
let TxResponse {
exit_reason,
logs,
used_gas,
let (
TxResponse {
exit_reason,
logs,
used_gas,
..
},
receipt,
..
} = executor.exec(&signed_tx);
) = executor.exec(&signed_tx);
debug!(
"receipt : {:#?} for signed_tx : {:#x}",
receipt,
Expand All @@ -122,8 +124,6 @@ impl Handlers {
gas_used += used_gas;
EVMHandler::logs_bloom(logs, &mut logs_bloom);
receipts_v3.push(receipt);

executor.commit();
}
QueueTx::BridgeTx(BridgeTx::EvmIn(BalanceUpdate { address, amount })) => {
debug!(
Expand All @@ -147,6 +147,8 @@ impl Handlers {
}
}
}

executor.commit();
}

if update_state {
Expand Down
1 change: 1 addition & 0 deletions lib/ain-evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod block;
mod ecrecover;
pub mod evm;
pub mod executor;
mod fee;
pub mod handler;
pub mod receipt;
pub mod runtime;
Expand Down
6 changes: 3 additions & 3 deletions lib/ain-evm/src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{backend::EVMBackendError, executor::TxResponse, transaction::SignedTx};
use ethereum::AccessList;
use ethereum::{AccessList, ReceiptV3};
use evm::Config;
use primitive_types::{H160, U256};

Expand All @@ -16,9 +16,9 @@ pub struct ExecutorContext<'a> {
pub trait Executor {
const CONFIG: Config = Config::shanghai();

fn call(&mut self, ctx: ExecutorContext, apply: bool) -> TxResponse;
fn call(&mut self, ctx: ExecutorContext) -> TxResponse;

fn exec(&mut self, tx: &SignedTx) -> TxResponse;
fn exec(&mut self, tx: &SignedTx) -> (TxResponse, ReceiptV3);
}

pub trait BridgeBackend {
Expand Down
2 changes: 1 addition & 1 deletion lib/ain-evm/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ impl SignedTx {
match &self.transaction {
TransactionV2::Legacy(tx) => tx.gas_price,
TransactionV2::EIP2930(tx) => tx.gas_price,
TransactionV2::EIP1559(tx) => tx.max_fee_per_gas.min(tx.max_priority_fee_per_gas), // TODO verify calculation
TransactionV2::EIP1559(tx) => tx.max_fee_per_gas,
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/functional/feature_evm.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def run_test(self):
assert_equal(block_txs[4], tx4)

# Check Eth balances before transfer
assert_equal(int(self.nodes[0].eth_getBalance(ethAddress)[2:], 16), 6000000000000000000)
assert_equal(int(self.nodes[0].eth_getBalance(ethAddress)[2:], 16), 5998236000000000000)
assert_equal(int(self.nodes[0].eth_getBalance(to_address)[2:], 16), 4000000000000000000)

# Check miner account balance after transfer
Expand Down
Loading

0 comments on commit 174e0ab

Please sign in to comment.