diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs
index 68708f5d7..73ce32444 100644
--- a/engine-precompiles/src/lib.rs
+++ b/engine-precompiles/src/lib.rs
@@ -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;
@@ -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 {
@@ -94,7 +97,7 @@ 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
>,
// Cannot be part of the generic precompiles because the `dyn` type-erasure messes with
// with the lifetime requirements on the type parameter `I`.
@@ -102,9 +105,12 @@ pub struct Precompiles<'a, I, E> {
pub ethereum_exit: ExitToEthereum,
pub predecessor_account_id: PredecessorAccount<'a, E>,
pub prepaid_gas: PrepaidGas<'a, E>,
+ pub promise_results: PromiseResult,
}
-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,
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -262,19 +269,20 @@ 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>,
- 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,
@@ -282,6 +290,7 @@ impl<'a, I: IO + Copy, E: Env> Precompiles<'a, I, E> {
ethereum_exit,
predecessor_account_id,
prepaid_gas,
+ promise_results,
}
}
@@ -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)
diff --git a/engine-precompiles/src/prepaid_gas.rs b/engine-precompiles/src/prepaid_gas.rs
index 041a23bce..9b59b7988 100644
--- a/engine-precompiles/src/prepaid_gas.rs
+++ b/engine-precompiles/src/prepaid_gas.rs
@@ -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..]`
diff --git a/engine-precompiles/src/promise_result.rs b/engine-precompiles/src/promise_result.rs
new file mode 100644
index 000000000..1899f66a6
--- /dev/null
+++ b/engine-precompiles/src/promise_result.rs
@@ -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 {
+ handler: H,
+}
+
+impl PromiseResult {
+ pub fn new(handler: H) -> Self {
+ Self { handler }
+ }
+}
+
+impl Precompile for PromiseResult {
+ fn required_gas(_input: &[u8]) -> Result {
+ // 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,
+ _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())
+ );
+ }
+}
diff --git a/engine-sdk/src/near_runtime.rs b/engine-sdk/src/near_runtime.rs
index 23a62b6a9..7852fd978 100644
--- a/engine-sdk/src/near_runtime.rs
+++ b/engine-sdk/src/near_runtime.rs
@@ -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() }
}
@@ -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.
diff --git a/engine-sdk/src/promise.rs b/engine-sdk/src/promise.rs
index 86a2b85bc..c6f0bac73 100644
--- a/engine-sdk/src/promise.rs
+++ b/engine-sdk/src/promise.rs
@@ -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;
@@ -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;
+}
+
+impl ReadOnlyPromiseHandler for T {
+ fn ro_promise_results_count(&self) -> u64 {
+ self.promise_results_count()
+ }
+
+ fn ro_promise_result(&self, index: u64) -> Option {
+ 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 {
+ 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
+ }
}
diff --git a/engine-standalone-storage/src/promise.rs b/engine-standalone-storage/src/promise.rs
index 2293fa33d..19ff476d9 100644
--- a/engine-standalone-storage/src/promise.rs
+++ b/engine-standalone-storage/src/promise.rs
@@ -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>],
+}
+
+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 {
- None
+ fn promise_result(&self, index: u64) -> Option {
+ 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 {
@@ -31,4 +46,8 @@ impl PromiseHandler for Noop {
}
fn promise_return(&mut self, _promise: PromiseId) {}
+
+ fn read_only(&self) -> Self::ReadOnly {
+ *self
+ }
}
diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs
index 9771b4e75..b2f7b68a6 100644
--- a/engine-standalone-storage/src/relayer_db/mod.rs
+++ b/engine-standalone-storage/src/relayer_db/mod.rs
@@ -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;
@@ -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)?;
}
@@ -256,6 +258,7 @@ mod test {
caller: "aurora".parse().unwrap(),
attached_near: 0,
transaction: TransactionKind::Unknown,
+ promise_data: Vec::new(),
},
&diff,
)
diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs
index 6f233e8a0..89c6d2f48 100644
--- a/engine-standalone-storage/src/sync/mod.rs
+++ b/engine-standalone-storage/src/sync/mod.rs
@@ -128,7 +128,9 @@ fn execute_transaction<'db>(
TransactionKind::Submit(tx) => {
// We can ignore promises in the standalone engine because it processes each receipt separately
// and it is fed a stream of receipts (it does not schedule them)
- let mut handler = crate::promise::Noop;
+ let mut handler = crate::promise::NoScheduler {
+ promise_data: &transaction_message.promise_data,
+ };
let transaction_bytes: Vec = tx.into();
let tx_hash = aurora_engine_sdk::keccak(&transaction_bytes);
@@ -151,7 +153,13 @@ fn execute_transaction<'db>(
}
other => {
- let result = non_submit_execute(other, io, env, relayer_address);
+ let result = non_submit_execute(
+ other,
+ io,
+ env,
+ relayer_address,
+ &transaction_message.promise_data,
+ );
(near_receipt_id, result)
}
};
@@ -169,11 +177,12 @@ fn non_submit_execute<'db>(
mut io: EngineStateAccess<'db, 'db, 'db>,
env: env::Fixed,
relayer_address: Address,
+ promise_data: &[Option>],
) -> Result