Skip to content

Commit

Permalink
EVM in/out refinements (#1865)
Browse files Browse the repository at this point in the history
* Return error on sub balance failure.

* Change to expected return type

* Update EVM in/out TX

* Use array instead of rust::Vec

* Pass context to CustomTxVisit

* Add temporary state to TransactionQueue

* Improve readability

* Block integration test

---------

Co-authored-by: Bushstar <[email protected]>
Co-authored-by: Prasanna Loganathar <[email protected]>
  • Loading branch information
3 people authored Apr 6, 2023
1 parent 3e943f9 commit 35c85d4
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 43 deletions.
2 changes: 2 additions & 0 deletions src/amount.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ struct DCT_ID {

static constexpr CAmount COIN = 100000000;
static constexpr CAmount CENT = 1000000;
static constexpr int64_t WEI_IN_GWEI = 1000000000;
static constexpr int64_t CAMOUNT_TO_WEI = 10;

//Converts the given value to decimal format string with COIN precision.
inline std::string GetDecimalString(CAmount nValue)
Expand Down
52 changes: 41 additions & 11 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3834,12 +3834,6 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor {
const auto sumFrom = SumAllTransfers(obj.from);
const auto sumTo = SumAllTransfers(obj.to);

if (obj.type == CTransferBalanceType::EvmIn || obj.type == CTransferBalanceType::EvmOut) {
for (const auto& [id, _] : sumFrom.balances)
if (id != DCT_ID{0})
return Res::Err("For EVM in/out transfers only DFI token is currently supported");
}

if (sumFrom != sumTo)
return Res::Err("sum of inputs (from) != sum of outputs (to)");

Expand All @@ -3851,18 +3845,54 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor {
if (!res)
return res;
} else if (obj.type == CTransferBalanceType::EvmIn) {
for (const auto& [addr, _] : obj.to) {
res = SubBalancesDelShares(obj.from);
if (!res)
return res;

for (const auto& [addr, balances] : obj.to) {
CTxDestination dest;
if (ExtractDestination(addr, dest)) {
if (dest.index() != WitV16KeyEthHashType) {
return Res::Err("To address must be an ETH address in case of \"evmin\" transfertype");
}
}

const auto toAddress = std::get<WitnessV16EthHash>(dest);

for (const auto& [id, amount] : balances.balances) {
if (id != DCT_ID{0}) {
return Res::Err("For EVM out transfers, only DFI token is currently supported");
}

arith_uint256 balanceIn = amount;
balanceIn *= CAMOUNT_TO_WEI * WEI_IN_GWEI;
evm_add_balance(evmContext, HexStr(toAddress.begin(), toAddress.end()), ArithToUint256(balanceIn).ToArrayReversed());
}
}
res = SubBalancesDelShares(obj.from);
if (!res)
return res;
} else if (obj.type == CTransferBalanceType::EvmOut) {
for (const auto& [addr, balances] : obj.from) {
CTxDestination dest;
if (ExtractDestination(addr, dest)) {
if (dest.index() != WitV16KeyEthHashType) {
return Res::Err("Invalid destination");
}
}

const auto fromAddress = std::get<WitnessV16EthHash>(dest);

for (const auto& [id, amount] : balances.balances) {
if (id != DCT_ID{0}) {
return Res::Err("For EVM out transfers, only DFI token is currently supported");
}

arith_uint256 balanceIn = amount;
balanceIn *= CAMOUNT_TO_WEI * WEI_IN_GWEI;
if (!evm_sub_balance(evmContext, HexStr(fromAddress.begin(), fromAddress.end()), ArithToUint256(balanceIn).ToArrayReversed())) {
return Res::Err("Not enough balance in %s to cover EVM out", EncodeDestination(dest));
}
}
}

res = AddBalancesSetShares(obj.to);
if (!res)
return res;
Expand Down Expand Up @@ -4085,7 +4115,7 @@ Res ApplyCustomTx(CCustomCSView &mnview,
PopulateVaultHistoryData(mnview.GetHistoryWriters(), view, txMessage, txType, height, txn, tx.GetHash());
}

res = CustomTxVisit(view, coins, tx, height, consensus, txMessage, time, txn);
res = CustomTxVisit(view, coins, tx, height, consensus, txMessage, time, txn, evmContext);

if (res) {
if (canSpend && txType == CustomTxType::UpdateMasternode) {
Expand Down
3 changes: 0 additions & 3 deletions src/masternodes/rpc_evm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
#include <libain_evm.h>
#include <key_io.h>

const int64_t WEI_IN_GWEI = 1000000000;
const int64_t CAMOUNT_TO_WEI = 10;

UniValue evmtx(const JSONRPCRequest& request) {
auto pwallet = GetWallet(request);

Expand Down
25 changes: 19 additions & 6 deletions src/rust/crates/ain-evm-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use std::error::Error;
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn evm_add_balance(address: &str, amount: [u8; 32]) -> Result<()>;
fn evm_sub_balance(address: &str, amount: [u8; 32]) -> Result<()>;
fn evm_add_balance(context: u64, address: &str, amount: [u8; 32]) -> Result<()>;
fn evm_sub_balance(context: u64, address: &str, amount: [u8; 32]) -> Result<bool>;
fn evm_validate_raw_tx(tx: &str) -> Result<bool>;

fn evm_get_context() -> u64;
Expand All @@ -33,12 +33,25 @@ mod ffi {
}
}

pub fn evm_add_balance(address: &str, amount: [u8; 32]) -> Result<(), Box<dyn Error>> {
RUNTIME.handlers.evm.add_balance(address, amount.into())
pub fn evm_add_balance(
context: u64,
address: &str,
amount: [u8; 32],
) -> Result<(), Box<dyn Error>> {
let address = address.parse()?;
Ok(RUNTIME.evm.add_balance(context, address, amount.into()))
}

pub fn evm_sub_balance(address: &str, amount: [u8; 32]) -> Result<(), Box<dyn Error>> {
RUNTIME.handlers.evm.sub_balance(address, amount.into())
pub fn evm_sub_balance(
context: u64,
address: &str,
amount: [u8; 32],
) -> Result<bool, Box<dyn Error>> {
let address = address.parse()?;
match RUNTIME.evm.sub_balance(context, address, amount.into()) {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}

pub fn evm_validate_raw_tx(tx: &str) -> Result<bool, Box<dyn Error>> {
Expand Down
153 changes: 151 additions & 2 deletions src/rust/crates/ain-evm-state/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,157 @@ pub struct Handlers {
impl Handlers {
pub fn new() -> Self {
Self {
evm: EVMHandler::new(),
block: BlockHandler::new(),
state: Arc::new(RwLock::new(EVMState::new())),
tx_queues: Arc::new(TransactionQueueMap::new()),
}
}

pub fn call(
&self,
caller: Option<H160>,
to: Option<H160>,
value: U256,
data: &[u8],
gas_limit: u64,
access_list: AccessList,
) -> (ExitReason, Vec<u8>) {
// TODO Add actual gas, chain_id, block_number from header
let vicinity = get_vicinity(caller, None);

let state = self.state.read().unwrap().clone();
let backend = MemoryBackend::new(&vicinity, state);
let tx_response =
AinExecutor::new(backend).call(caller, to, value, data, gas_limit, access_list, false);
(tx_response.exit_reason, tx_response.data)
}

// TODO wrap in EVM transaction and dryrun with evm_call
pub fn add_balance(&self, context: u64, address: H160, value: U256) {
self.tx_queues.add_balance(context, address, value)
}

pub fn sub_balance(
&self,
context: u64,
address: H160,
value: U256,
) -> Result<(), Box<dyn Error>> {
self.tx_queues
.sub_balance(context, address, value)
.map_err(|e| e.into())
}

pub fn validate_raw_tx(&self, tx: &str) -> Result<SignedTx, Box<dyn Error>> {
let buffer = <Vec<u8>>::from_hex(tx)?;
let tx: TransactionV2 = ethereum::EnvelopedDecodable::decode(&buffer)
.map_err(|_| anyhow!("Error: decoding raw tx to TransactionV2"))?;

// TODO Validate gas limit and chain_id

let signed_tx: SignedTx = tx.try_into()?;

// TODO validate account nonce and balance to pay gas
// let account = self.get_account(&signed_tx.sender);
// if account.nonce >= signed_tx.nonce() {
// return Err(anyhow!("Invalid nonce").into());
// }
// if account.balance < MIN_GAS {
// return Err(anyhow!("Insufficiant balance to pay fees").into());
// }

match self.call(
Some(signed_tx.sender),
signed_tx.to(),
signed_tx.value(),
signed_tx.data(),
signed_tx.gas_limit().as_u64(),
signed_tx.access_list(),
) {
(exit_reason, _) if exit_reason.is_succeed() => Ok(signed_tx),
_ => Err(anyhow!("Error calling EVM").into()),
}
}

pub fn get_context(&self) -> u64 {
let state = self.state.read().unwrap().clone();
self.tx_queues.get_context(state)
}

pub fn discard_context(&self, context: u64) {
self.tx_queues.clear(context)
}

pub fn queue_tx(&self, context: u64, raw_tx: &str) -> Result<(), Box<dyn Error>> {
let signed_tx = self.validate_raw_tx(raw_tx)?;
self.tx_queues.add_signed_tx(context, signed_tx);
Ok(())
}

pub fn finalize_block(
&self,
context: u64,
update_state: bool,
) -> Result<(Block<TransactionV2>, Vec<TransactionV2>), Box<dyn Error>> {
let mut tx_hashes = Vec::with_capacity(self.tx_queues.len(context));
let mut failed_tx_hashes = Vec::with_capacity(self.tx_queues.len(context));
let vicinity = get_vicinity(None, None);
let state = self.tx_queues.state(context).expect("Wrong context");
let backend = MemoryBackend::new(&vicinity, state);
let mut executor = AinExecutor::new(backend);

for signed_tx in self.tx_queues.drain_all(context) {
let tx_response = executor.exec(&signed_tx);
println!("tx_response : {:#?}", tx_response);
if tx_response.exit_reason.is_succeed() {
// responses.push()
tx_hashes.push(signed_tx.transaction);
} else {
failed_tx_hashes.push(signed_tx.transaction)
}
}

self.tx_queues.remove(context);

if update_state {
let mut state = self.state.write().unwrap();
*state = executor.backend().state().clone();
}

let block = Block::new(
PartialHeader {
parent_hash: Default::default(),
beneficiary: Default::default(),
state_root: Default::default(),
receipts_root: Default::default(),
logs_bloom: Default::default(),
difficulty: Default::default(),
number: Default::default(),
gas_limit: Default::default(),
gas_used: Default::default(),
timestamp: Default::default(),
extra_data: Default::default(),
mix_hash: Default::default(),
nonce: Default::default(),
},
tx_hashes,
Vec::new(),
);
Ok((block, failed_tx_hashes))
}
}

// TBD refine what vicinity we need. gas_price and origin only ?
fn get_vicinity(origin: Option<H160>, gas_price: Option<U256>) -> MemoryVicinity {
MemoryVicinity {
gas_price: gas_price.unwrap_or_else(|| U256::MAX),
origin: origin.unwrap_or_default(),
block_hashes: Vec::new(),
block_number: Default::default(),
block_coinbase: Default::default(),
block_timestamp: Default::default(),
block_difficulty: Default::default(),
block_gas_limit: U256::MAX,
chain_id: U256::one(),
block_base_fee_per_gas: U256::MAX,
}
}
Loading

0 comments on commit 35c85d4

Please sign in to comment.