Skip to content

Commit

Permalink
Implement EvmDb for WorkingSet (Sovereign-Labs#424)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkolad authored Jun 22, 2023
1 parent f54d926 commit 524dd2a
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 42 deletions.
1 change: 1 addition & 0 deletions module-system/module-implementations/sov-evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ sov-state = { path = "../../sov-state", version = "0.1", default-features = fals
sov-rollup-interface = { path = "../../../rollup-interface", version = "0.1" }

anyhow = { workspace = true }
bytes = { workspace = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
thiserror = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use bytes::Bytes;

use super::AccountInfo;
use revm::primitives::{AccountInfo as ReVmAccountInfo, U256};
use revm::primitives::{Bytecode, B256};

impl From<AccountInfo> for ReVmAccountInfo {
fn from(info: AccountInfo) -> Self {
Self {
nonce: info.nonce,
balance: U256::from_le_bytes(info.balance),
code: Some(Bytecode::new_raw(Bytes::from(info.code))),
code_hash: B256::from(info.code_hash),
}
}
}

impl From<ReVmAccountInfo> for AccountInfo {
fn from(info: ReVmAccountInfo) -> Self {
Self {
balance: info.balance.to_le_bytes(),
code_hash: info.code_hash.to_fixed_bytes(),
code: info.code.unwrap_or_default().bytes().to_vec(),
nonce: info.nonce,
}
}
}
58 changes: 37 additions & 21 deletions module-system/module-implementations/sov-evm/src/evm/db.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
use std::convert::Infallible;

use super::{Address, DbAccount};
use revm::{
db::{CacheDB, EmptyDB},
primitives::{Account, AccountInfo, Bytecode, HashMap, B160, B256, U256},
Database, DatabaseCommit,
primitives::{AccountInfo as ReVmAccountInfo, Bytecode, B160, B256, U256},
Database,
};
use sov_state::WorkingSet;
use std::convert::Infallible;

pub(crate) struct EvmDb<'a> {
pub(crate) db: &'a mut CacheDB<EmptyDB>,
pub(crate) struct EvmDb<'a, C: sov_modules_api::Context> {
pub(crate) accounts: sov_state::StateMap<Address, DbAccount>,
pub(crate) working_set: &'a mut WorkingSet<C::Storage>,
}

impl<'a> Database for EvmDb<'a> {
impl<'a, C: sov_modules_api::Context> EvmDb<'a, C> {
pub(crate) fn new(
accounts: sov_state::StateMap<Address, DbAccount>,
working_set: &'a mut WorkingSet<C::Storage>,
) -> Self {
Self {
accounts,
working_set,
}
}
}

impl<'a, C: sov_modules_api::Context> Database for EvmDb<'a, C> {
type Error = Infallible;

fn basic(&mut self, address: B160) -> Result<Option<AccountInfo>, Self::Error> {
self.db.basic(address)
fn basic(&mut self, address: B160) -> Result<Option<ReVmAccountInfo>, Self::Error> {
let db_account = self.accounts.get(&address.0, self.working_set);
Ok(db_account.map(|acc| acc.info.into()))
}

fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.db.code_by_hash(code_hash)
fn code_by_hash(&mut self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
panic!("Should not be called. Contract code is already loaded");
}

fn storage(&mut self, address: B160, index: U256) -> Result<U256, Self::Error> {
self.db.storage(address, index)
}

fn block_hash(&mut self, number: U256) -> Result<B256, Self::Error> {
self.db.block_hash(number)
let storage_value = if let Some(acc) = self.accounts.get(&address.0, self.working_set) {
let key = index.to_le_bytes();
let storage_value = acc.storage.get(&key, self.working_set).unwrap_or_default();
U256::from_le_bytes(storage_value)
} else {
U256::default()
};

Ok(storage_value)
}
}

impl<'a> DatabaseCommit for EvmDb<'a> {
fn commit(&mut self, changes: HashMap<B160, Account>) {
self.db.commit(changes)
fn block_hash(&mut self, _number: U256) -> Result<B256, Self::Error> {
todo!("block_hash not yet implemented")
}
}
36 changes: 36 additions & 0 deletions module-system/module-implementations/sov-evm/src/evm/db_commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use super::{db::EvmDb, DbAccount};
use revm::{
primitives::{Account, HashMap, B160},
DatabaseCommit,
};

impl<'a, C: sov_modules_api::Context> DatabaseCommit for EvmDb<'a, C> {
fn commit(&mut self, changes: HashMap<B160, Account>) {
for (address, account) in changes {
let address = address.0;

// TODO figure out what to do when account is destroyed.
// https://github.com/Sovereign-Labs/sovereign-sdk/issues/425
if account.is_destroyed {
todo!("Account destruction not supported")
}

let accounts_prefix = self.accounts.prefix();

let mut db_account = self
.accounts
.get(&address, self.working_set)
.unwrap_or_else(|| DbAccount::new(accounts_prefix, address));

db_account.info = account.info.into();

for (key, value) in account.storage.into_iter() {
let key = key.to_le_bytes();
let value = value.present_value().to_le_bytes();
db_account.storage.set(&key, &value, self.working_set);
}

self.accounts.set(&address, &db_account, self.working_set)
}
}
}
27 changes: 27 additions & 0 deletions module-system/module-implementations/sov-evm/src/evm/db_init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::{db::EvmDb, AccountInfo, Address, DbAccount};
#[cfg(test)]
use revm::{
db::{CacheDB, EmptyDB},
primitives::B160,
};

/// Initializes database with a predefined account.
pub(crate) trait InitEvmDb {
fn insert_account_info(&mut self, address: Address, acc: AccountInfo);
}

impl<'a, C: sov_modules_api::Context> InitEvmDb for EvmDb<'a, C> {
fn insert_account_info(&mut self, sender: Address, info: AccountInfo) {
let parent_prefix = self.accounts.prefix();
let db_account = DbAccount::new_with_info(parent_prefix, sender, info);

self.accounts.set(&sender, &db_account, self.working_set);
}
}

#[cfg(test)]
impl InitEvmDb for CacheDB<EmptyDB> {
fn insert_account_info(&mut self, sender: Address, acc: AccountInfo) {
self.insert_account_info(B160::from_slice(&sender), acc.into());
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use super::db::EvmDb;
use std::convert::Infallible;

use revm::{
self,
primitives::{EVMError, ExecutionResult, TxEnv},
Database, DatabaseCommit,
};
use std::convert::Infallible;

#[allow(dead_code)]
pub(crate) fn execute_tx(
db: EvmDb,
pub(crate) fn execute_tx<DB: Database<Error = Infallible> + DatabaseCommit>(
db: DB,
tx_env: TxEnv,
) -> Result<ExecutionResult, EVMError<Infallible>> {
let mut evm = revm::new();
Expand Down
52 changes: 51 additions & 1 deletion module-system/module-implementations/sov-evm/src/evm/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,54 @@
mod db;
use sov_state::Prefix;

mod conversions;
pub(crate) mod db;
mod db_commit;
mod db_init;
mod executor;
#[cfg(test)]
mod tests;

pub(crate) type Address = [u8; 20];
pub(crate) type SovU256 = [u8; 32];

// Stores information about an EVM account
#[derive(borsh::BorshDeserialize, borsh::BorshSerialize, Debug, PartialEq, Clone, Default)]
pub(crate) struct AccountInfo {
pub(crate) balance: SovU256,
pub(crate) code_hash: SovU256,
// TODO: `code` can be a huge chunk of data. We can use `StateValue` and lazy load it only when needed.
// https://github.com/Sovereign-Labs/sovereign-sdk/issues/425
pub(crate) code: Vec<u8>,
pub(crate) nonce: u64,
}

/// Stores information about an EVM account and a corresponding account state.
#[derive(borsh::BorshDeserialize, borsh::BorshSerialize, Debug, PartialEq, Clone)]
pub(crate) struct DbAccount {
pub(crate) info: AccountInfo,
pub(crate) storage: sov_state::StateMap<SovU256, SovU256>,
}

impl DbAccount {
fn new(parent_prefix: &Prefix, address: Address) -> Self {
let prefix = Self::create_storage_prefix(parent_prefix, address);
Self {
info: Default::default(),
storage: sov_state::StateMap::new(prefix),
}
}

fn new_with_info(parent_prefix: &Prefix, address: Address, info: AccountInfo) -> Self {
let prefix = Self::create_storage_prefix(parent_prefix, address);
Self {
info,
storage: sov_state::StateMap::new(prefix),
}
}

fn create_storage_prefix(parent_prefix: &Prefix, address: Address) -> Prefix {
let mut prefix = parent_prefix.as_aligned_vec().clone().into_inner();
prefix.extend_from_slice(&address);
Prefix::new(prefix)
}
}
51 changes: 36 additions & 15 deletions module-system/module-implementations/sov-evm/src/evm/tests.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use super::{db::EvmDb, executor};
use crate::{evm::AccountInfo, Evm};

use super::{db::EvmDb, db_init::InitEvmDb, executor};
use bytes::Bytes;
use ethereum_types::U256 as EU256;
use ethers_contract::BaseContract;
use ethers_core::abi::Abi;
use revm::{
db::CacheDB,
primitives::{
AccountInfo, ExecutionResult, Output, TransactTo, TxEnv, B160, KECCAK_EMPTY, U256,
},
primitives::{ExecutionResult, Output, TransactTo, TxEnv, B160, KECCAK_EMPTY, U256},
Database, DatabaseCommit,
};
use std::{path::PathBuf, str::FromStr};
use sov_state::{ProverStorage, WorkingSet};
use std::{convert::Infallible, path::PathBuf};

type C = sov_modules_api::default_context::DefaultContext;

fn output(result: ExecutionResult) -> Bytes {
match result {
Expand Down Expand Up @@ -46,17 +50,34 @@ fn make_contract_from_abi(path: PathBuf) -> BaseContract {
}

#[test]
fn simple_contract_execution() {
let caller = B160::from_str("0x1000000000000000000000000000000000000000").unwrap();
let mut db = CacheDB::default();
fn simple_contract_execution_sov_state() {
let tmpdir = tempfile::tempdir().unwrap();
let mut working_set: WorkingSet<<C as sov_modules_api::Spec>::Storage> =
WorkingSet::new(ProverStorage::with_path(tmpdir.path()).unwrap());

let evm = Evm::<C>::default();
let evm_db: EvmDb<'_, C> = evm.get_db(&mut working_set);

simple_contract_execution(evm_db);
}

#[test]
fn simple_contract_execution_in_memory_state() {
let db = CacheDB::default();
simple_contract_execution(db);
}

db.insert_account_info(
fn simple_contract_execution<DB: Database<Error = Infallible> + DatabaseCommit + InitEvmDb>(
mut evm_db: DB,
) {
let caller: [u8; 20] = [11; 20];
evm_db.insert_account_info(
caller,
AccountInfo {
balance: U256::from(1000000000).to_le_bytes(),
code_hash: KECCAK_EMPTY.to_fixed_bytes(),
code: vec![],
nonce: 1,
balance: U256::from(1000000000),
code: None,
code_hash: KECCAK_EMPTY,
},
);

Expand All @@ -73,7 +94,7 @@ fn simple_contract_execution() {
..Default::default()
};

let result = executor::execute_tx(EvmDb { db: &mut db }, tx_env).unwrap();
let result = executor::execute_tx(&mut evm_db, tx_env).unwrap();
contract_address(result)
};

Expand All @@ -93,7 +114,7 @@ fn simple_contract_execution() {
..Default::default()
};

executor::execute_tx(EvmDb { db: &mut db }, tx_env).unwrap();
executor::execute_tx(&mut evm_db, tx_env).unwrap();
}

let get_res = {
Expand All @@ -105,7 +126,7 @@ fn simple_contract_execution() {
..Default::default()
};

let result = executor::execute_tx(EvmDb { db: &mut db }, tx_env).unwrap();
let result = executor::execute_tx(&mut evm_db, tx_env).unwrap();

let out = output(result);
EU256::from(out.as_ref())
Expand Down
12 changes: 12 additions & 0 deletions module-system/module-implementations/sov-evm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use evm::{db::EvmDb, Address, DbAccount};
use sov_modules_api::Error;
use sov_modules_macros::ModuleInfo;
use sov_state::WorkingSet;
Expand All @@ -8,10 +9,14 @@ pub mod genesis;
#[cfg(feature = "native")]
pub mod query;

#[allow(dead_code)]
#[derive(ModuleInfo, Clone)]
pub struct Evm<C: sov_modules_api::Context> {
#[address]
pub(crate) address: C::Address,

#[state]
accounts: sov_state::StateMap<Address, DbAccount>,
}

impl<C: sov_modules_api::Context> sov_modules_api::Module for Evm<C> {
Expand All @@ -38,3 +43,10 @@ impl<C: sov_modules_api::Context> sov_modules_api::Module for Evm<C> {
todo!()
}
}

impl<C: sov_modules_api::Context> Evm<C> {
#[allow(dead_code)]
pub(crate) fn get_db<'a>(&self, working_set: &'a mut WorkingSet<C::Storage>) -> EvmDb<'a, C> {
EvmDb::new(self.accounts.clone(), working_set)
}
}

0 comments on commit 524dd2a

Please sign in to comment.