Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(engine): Get promise results precompile #575

Merged
merged 6 commits into from
Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions engine-precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod modexp;
pub mod native;
mod prelude;
pub mod prepaid_gas;
pub mod promise_result;
pub mod random;
pub mod secp256k1;
mod utils;
Expand All @@ -30,10 +31,12 @@ use crate::random::RandomSeed;
use crate::secp256k1::ECRecover;
use aurora_engine_sdk::env::Env;
use aurora_engine_sdk::io::IO;
use aurora_engine_sdk::promise::ReadOnlyPromiseHandler;
use aurora_engine_types::{account_id::AccountId, types::Address, vec, BTreeMap, Box};
use evm::backend::Log;
use evm::executor::{self, stack::PrecompileHandle};
use evm::{Context, ExitError, ExitSucceed};
use promise_result::PromiseResult;

#[derive(Debug, Default)]
pub struct PrecompileOutput {
Expand Down Expand Up @@ -94,17 +97,20 @@ impl HardFork for Istanbul {}

impl HardFork for Berlin {}

pub struct Precompiles<'a, I, E> {
pub struct Precompiles<'a, I, E, H> {
pub generic_precompiles: prelude::BTreeMap<Address, Box<dyn Precompile>>,
// Cannot be part of the generic precompiles because the `dyn` type-erasure messes with
// with the lifetime requirements on the type parameter `I`.
pub near_exit: ExitToNear<I>,
pub ethereum_exit: ExitToEthereum<I>,
pub predecessor_account_id: PredecessorAccount<'a, E>,
pub prepaid_gas: PrepaidGas<'a, E>,
pub promise_results: PromiseResult<H>,
}

impl<'a, I: IO + Copy, E: Env> executor::stack::PrecompileSet for Precompiles<'a, I, E> {
impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::PrecompileSet
for Precompiles<'a, I, E, H>
{
fn execute(
&self,
handle: &mut impl PrecompileHandle,
Expand Down Expand Up @@ -139,16 +145,17 @@ impl<'a, I: IO + Copy, E: Env> executor::stack::PrecompileSet for Precompiles<'a
}
}

pub struct PrecompileConstructorContext<'a, I, E> {
pub struct PrecompileConstructorContext<'a, I, E, H> {
pub current_account_id: AccountId,
pub random_seed: H256,
pub io: I,
pub env: &'a E,
pub promise_handler: H,
}

impl<'a, I: IO + Copy, E: Env> Precompiles<'a, I, E> {
impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, H> {
#[allow(dead_code)]
pub fn new_homestead(ctx: PrecompileConstructorContext<'a, I, E>) -> Self {
pub fn new_homestead(ctx: PrecompileConstructorContext<'a, I, E, H>) -> Self {
let addresses = vec![
ECRecover::ADDRESS,
SHA256::ADDRESS,
Expand All @@ -168,7 +175,7 @@ impl<'a, I: IO + Copy, E: Env> Precompiles<'a, I, E> {
}

#[allow(dead_code)]
pub fn new_byzantium(ctx: PrecompileConstructorContext<'a, I, E>) -> Self {
pub fn new_byzantium(ctx: PrecompileConstructorContext<'a, I, E, H>) -> Self {
let addresses = vec![
ECRecover::ADDRESS,
SHA256::ADDRESS,
Expand Down Expand Up @@ -198,7 +205,7 @@ impl<'a, I: IO + Copy, E: Env> Precompiles<'a, I, E> {
Self::with_generic_precompiles(map, ctx)
}

pub fn new_istanbul(ctx: PrecompileConstructorContext<'a, I, E>) -> Self {
pub fn new_istanbul(ctx: PrecompileConstructorContext<'a, I, E, H>) -> Self {
let addresses = vec![
ECRecover::ADDRESS,
SHA256::ADDRESS,
Expand Down Expand Up @@ -230,7 +237,7 @@ impl<'a, I: IO + Copy, E: Env> Precompiles<'a, I, E> {
Self::with_generic_precompiles(map, ctx)
}

pub fn new_berlin(ctx: PrecompileConstructorContext<'a, I, E>) -> Self {
pub fn new_berlin(ctx: PrecompileConstructorContext<'a, I, E, H>) -> Self {
let addresses = vec![
ECRecover::ADDRESS,
SHA256::ADDRESS,
Expand Down Expand Up @@ -262,26 +269,28 @@ impl<'a, I: IO + Copy, E: Env> Precompiles<'a, I, E> {
Self::with_generic_precompiles(map, ctx)
}

pub fn new_london(ctx: PrecompileConstructorContext<'a, I, E>) -> Self {
pub fn new_london(ctx: PrecompileConstructorContext<'a, I, E, H>) -> Self {
// no precompile changes in London HF
Self::new_berlin(ctx)
}

fn with_generic_precompiles(
generic_precompiles: BTreeMap<Address, Box<dyn Precompile>>,
ctx: PrecompileConstructorContext<'a, I, E>,
ctx: PrecompileConstructorContext<'a, I, E, H>,
) -> Self {
let near_exit = ExitToNear::new(ctx.current_account_id.clone(), ctx.io);
let ethereum_exit = ExitToEthereum::new(ctx.current_account_id, ctx.io);
let predecessor_account_id = PredecessorAccount::new(ctx.env);
let prepaid_gas = PrepaidGas::new(ctx.env);
let promise_results = PromiseResult::new(ctx.promise_handler);

Self {
generic_precompiles,
near_exit,
ethereum_exit,
predecessor_account_id,
prepaid_gas,
promise_results,
}
}

Expand All @@ -298,6 +307,8 @@ impl<'a, I: IO + Copy, E: Env> Precompiles<'a, I, E> {
return Some(f(&self.predecessor_account_id));
} else if address == prepaid_gas::ADDRESS {
return Some(f(&self.prepaid_gas));
} else if address == promise_result::ADDRESS {
return Some(f(&self.promise_results));
}
self.generic_precompiles
.get(&address)
Expand Down
2 changes: 1 addition & 1 deletion engine-precompiles/src/prepaid_gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use aurora_engine_sdk::env::Env;
use aurora_engine_types::{vec, U256};
use evm::{Context, ExitError};

/// predecessor_account_id precompile address
/// prepaid_gas precompile address
///
/// Address: `0x536822d27de53629ef1f84c60555689e9488609f`
/// This address is computed as: `&keccak("prepaidGas")[12..]`
Expand Down
90 changes: 90 additions & 0 deletions engine-precompiles/src/promise_result.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use super::{EvmPrecompileResult, Precompile};
use crate::prelude::types::{Address, EthGas};
use crate::PrecompileOutput;
use aurora_engine_sdk::promise::ReadOnlyPromiseHandler;
use aurora_engine_types::{Cow, Vec};
use borsh::BorshSerialize;
use evm::{Context, ExitError};

/// get_promise_results precompile address
///
/// Address: `0x0a3540f79be10ef14890e87c1a0040a68cc6af71`
/// This address is computed as: `&keccak("getPromiseResults")[12..]`
pub const ADDRESS: Address = crate::make_address(0x0a3540f7, 0x9be10ef14890e87c1a0040a68cc6af71);

pub mod costs {
use crate::prelude::types::EthGas;

/// This cost is always charged for calling this precompile.
pub const PROMISE_RESULT_BASE_COST: EthGas = EthGas::new(125);
/// This is the cost per byte of promise result data.
pub const PROMISE_RESULT_BYTE_COST: EthGas = EthGas::new(1);
}

pub struct PromiseResult<H> {
handler: H,
}

impl<H> PromiseResult<H> {
pub fn new(handler: H) -> Self {
Self { handler }
}
}

impl<H: ReadOnlyPromiseHandler> Precompile for PromiseResult<H> {
fn required_gas(_input: &[u8]) -> Result<EthGas, ExitError> {
// Only gives the cost we can know without reading any promise data.
// This allows failing fast in the case the base cost cannot even be covered.
Ok(costs::PROMISE_RESULT_BASE_COST)
}

fn run(
&self,
input: &[u8],
target_gas: Option<EthGas>,
_context: &Context,
_is_static: bool,
) -> EvmPrecompileResult {
let mut cost = Self::required_gas(input)?;
let check_cost = |cost: EthGas| -> Result<(), ExitError> {
if let Some(target_gas) = target_gas {
if cost > target_gas {
return Err(ExitError::OutOfGas);
}
}
Ok(())
};
check_cost(cost)?;

let num_promises = self.handler.ro_promise_results_count();
let n_usize = usize::try_from(num_promises).map_err(crate::utils::err_usize_conv)?;
let mut results = Vec::with_capacity(n_usize);
for i in 0..num_promises {
if let Some(result) = self.handler.ro_promise_result(i) {
let n_bytes = u64::try_from(result.size()).map_err(crate::utils::err_usize_conv)?;
cost += n_bytes * costs::PROMISE_RESULT_BYTE_COST;
check_cost(cost)?;
results.push(result);
}
}

let bytes = results
.try_to_vec()
.map_err(|_| ExitError::Other(Cow::Borrowed("ERR_PROMISE_RESULT_SERIALIZATION")))?;
Ok(PrecompileOutput::without_logs(cost, bytes))
}
}

#[cfg(test)]
mod tests {
use crate::prelude::sdk::types::near_account_to_evm_address;
use crate::promise_result;

#[test]
fn test_get_promise_results_precompile_id() {
assert_eq!(
promise_result::ADDRESS,
near_account_to_evm_address("getPromiseResults".as_bytes())
);
}
}
6 changes: 6 additions & 0 deletions engine-sdk/src/near_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ impl crate::env::Env for Runtime {
}

impl crate::promise::PromiseHandler for Runtime {
type ReadOnly = Self;

fn promise_results_count(&self) -> u64 {
unsafe { exports::promise_results_count() }
}
Expand Down Expand Up @@ -379,6 +381,10 @@ impl crate::promise::PromiseHandler for Runtime {
exports::promise_return(promise.raw());
}
}

fn read_only(&self) -> Self::ReadOnly {
Self
}
}

/// Some host functions are not usable in NEAR view calls.
Expand Down
57 changes: 57 additions & 0 deletions engine-sdk/src/promise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ impl PromiseId {
}

pub trait PromiseHandler {
type ReadOnly: ReadOnlyPromiseHandler;

fn promise_results_count(&self) -> u64;
fn promise_result(&self, index: u64) -> Option<PromiseResult>;

Expand All @@ -33,4 +35,59 @@ pub trait PromiseHandler {
let base = self.promise_create_call(&args.base);
self.promise_attach_callback(base, &args.callback)
}

fn read_only(&self) -> Self::ReadOnly;
}

pub trait ReadOnlyPromiseHandler {
fn ro_promise_results_count(&self) -> u64;
fn ro_promise_result(&self, index: u64) -> Option<PromiseResult>;
}

impl<T: PromiseHandler> ReadOnlyPromiseHandler for T {
fn ro_promise_results_count(&self) -> u64 {
self.promise_results_count()
}

fn ro_promise_result(&self, index: u64) -> Option<PromiseResult> {
self.promise_result(index)
}
}

/// A promise handler which does nothing. Should only be used when promises can be safely ignored.
#[derive(Debug, Copy, Clone)]
pub struct Noop;

impl PromiseHandler for Noop {
type ReadOnly = Self;

fn promise_results_count(&self) -> u64 {
0
}

fn promise_result(&self, _index: u64) -> Option<PromiseResult> {
None
}

fn promise_create_call(&mut self, _args: &PromiseCreateArgs) -> PromiseId {
PromiseId::new(0)
}

fn promise_attach_callback(
&mut self,
_base: PromiseId,
_callback: &PromiseCreateArgs,
) -> PromiseId {
PromiseId::new(0)
}

fn promise_create_batch(&mut self, _args: &PromiseBatchAction) -> PromiseId {
PromiseId::new(0)
}

fn promise_return(&mut self, _promise: PromiseId) {}

fn read_only(&self) -> Self::ReadOnly {
Self
}
}
31 changes: 25 additions & 6 deletions engine-standalone-storage/src/promise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,31 @@ use aurora_engine_sdk::promise::{PromiseHandler, PromiseId};
use aurora_engine_types::parameters::{PromiseBatchAction, PromiseCreateArgs};
use aurora_engine_types::types::PromiseResult;

/// A promise handler which does nothing. Should only be used when promises can be safely ignored.
pub struct Noop;
/// Implements `PromiseHandler` so that it can be used in the standalone engine implementation of
/// methods like `call`, however since the standalone engine cannot schedule promises in a
/// meaningful way, the mutable implementations are no-ops. Functionally, this is only an implementation
/// of `ReadOnlyPromiseHandler`, which is needed for the standalone engine to properly serve the
/// EVM precompile that gives back information on the results of promises (possibly scheduled using
/// the cross-contract calls feature).
#[derive(Debug, Clone, Copy)]
pub struct NoScheduler<'a> {
pub promise_data: &'a [Option<Vec<u8>>],
}

impl<'a> PromiseHandler for NoScheduler<'a> {
type ReadOnly = Self;

impl PromiseHandler for Noop {
fn promise_results_count(&self) -> u64 {
0
u64::try_from(self.promise_data.len()).unwrap_or_default()
}

fn promise_result(&self, _index: u64) -> Option<PromiseResult> {
None
fn promise_result(&self, index: u64) -> Option<PromiseResult> {
let i = usize::try_from(index).ok()?;
let result = match self.promise_data.get(i)? {
Some(bytes) => PromiseResult::Successful(bytes.clone()),
None => PromiseResult::Failed,
};
Some(result)
}

fn promise_create_call(&mut self, _args: &PromiseCreateArgs) -> PromiseId {
Expand All @@ -31,4 +46,8 @@ impl PromiseHandler for Noop {
}

fn promise_return(&mut self, _promise: PromiseId) {}

fn read_only(&self) -> Self::ReadOnly {
*self
}
}
5 changes: 4 additions & 1 deletion engine-standalone-storage/src/relayer_db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ where
random_seed: H256::zero(),
prepaid_gas: DEFAULT_PREPAID_GAS,
};
let mut handler = crate::promise::Noop;
// We use the Noop handler here since the relayer DB does not contain any promise information.
let mut handler = aurora_engine_sdk::promise::Noop;

while let Some(row) = rows.next()? {
let near_tx_hash = row.near_hash;
Expand Down Expand Up @@ -150,6 +151,7 @@ where
caller: env.predecessor_account_id(),
attached_near: 0,
transaction: crate::sync::types::TransactionKind::Submit(tx),
promise_data: Vec::new(),
};
storage.set_transaction_included(tx_hash, &tx_msg, &diff)?;
}
Expand Down Expand Up @@ -256,6 +258,7 @@ mod test {
caller: "aurora".parse().unwrap(),
attached_near: 0,
transaction: TransactionKind::Unknown,
promise_data: Vec::new(),
},
&diff,
)
Expand Down
Loading