synor/crates/synor-vm/src/context.rs
Gulshan Yadav 48949ebb3f Initial commit: Synor blockchain monorepo
A complete blockchain implementation featuring:
- synord: Full node with GHOSTDAG consensus
- explorer-web: Modern React blockchain explorer with 3D DAG visualization
- CLI wallet and tools
- Smart contract SDK and example contracts (DEX, NFT, token)
- WASM crypto library for browser/mobile
2026-01-08 05:22:17 +05:30

467 lines
12 KiB
Rust

//! Execution context for contract calls.
//!
//! The context provides contracts with information about the current
//! execution environment (caller, value, block info, etc.).
use synor_types::{Address, Hash256, Network};
use crate::gas::GasMeter;
use crate::storage::{ContractStorage, MemoryStorage};
use crate::ContractId;
/// Block information available to contracts.
#[derive(Clone, Debug)]
pub struct BlockInfo {
/// Block height.
pub height: u64,
/// Block timestamp (Unix milliseconds).
pub timestamp: u64,
/// Block hash (of selected parent).
pub hash: Hash256,
/// Blue score.
pub blue_score: u64,
/// DAA score (for difficulty).
pub daa_score: u64,
/// Coinbase address.
pub coinbase: Address,
}
impl Default for BlockInfo {
fn default() -> Self {
BlockInfo {
height: 0,
timestamp: 0,
hash: Hash256::default(),
blue_score: 0,
daa_score: 0,
coinbase: Address::from_ed25519_pubkey(Network::Mainnet, &[0; 32]),
}
}
}
/// Transaction information.
#[derive(Clone, Debug)]
pub struct TransactionInfo {
/// Transaction hash.
pub hash: Hash256,
/// Transaction origin (original sender).
pub origin: Address,
/// Gas price.
pub gas_price: u64,
/// Gas limit.
pub gas_limit: u64,
}
impl Default for TransactionInfo {
fn default() -> Self {
TransactionInfo {
hash: Hash256::default(),
origin: Address::from_ed25519_pubkey(Network::Mainnet, &[0; 32]),
gas_price: 1,
gas_limit: 10_000_000,
}
}
}
/// Call-specific context.
#[derive(Clone, Debug)]
pub struct CallContext {
/// The contract being called.
pub contract: ContractId,
/// The caller (previous contract or EOA).
pub caller: Address,
/// Value sent with the call.
pub value: u64,
/// Input data (method + args).
pub input: Vec<u8>,
/// Whether this is a delegate call.
pub is_delegate: bool,
/// Whether this is a static call (read-only).
pub is_static: bool,
/// Call depth.
pub depth: u32,
}
impl CallContext {
/// Creates a new call context.
pub fn new(contract: ContractId, caller: Address, value: u64, input: Vec<u8>) -> Self {
CallContext {
contract,
caller,
value,
input,
is_delegate: false,
is_static: false,
depth: 0,
}
}
/// Creates a static (read-only) call context.
pub fn static_call(contract: ContractId, caller: Address, input: Vec<u8>) -> Self {
CallContext {
contract,
caller,
value: 0,
input,
is_delegate: false,
is_static: true,
depth: 0,
}
}
/// Creates a delegate call context.
pub fn delegate_call(
contract: ContractId,
caller: Address,
value: u64,
input: Vec<u8>,
) -> Self {
CallContext {
contract,
caller,
value,
input,
is_delegate: true,
is_static: false,
depth: 0,
}
}
/// Increments call depth for nested calls.
pub fn nested(&self, contract: ContractId, value: u64, input: Vec<u8>) -> Self {
CallContext {
contract,
// Current contract becomes caller - use contract hash as "address"
caller: Address::from_ed25519_pubkey(Network::Mainnet, self.contract.as_bytes()),
value,
input,
is_delegate: false,
is_static: self.is_static, // Inherit static flag
depth: self.depth + 1,
}
}
}
/// Full execution context.
pub struct ExecutionContext<S: ContractStorage = MemoryStorage> {
/// Block information.
pub block: BlockInfo,
/// Transaction information.
pub tx: TransactionInfo,
/// Current call context.
pub call: CallContext,
/// Gas meter.
pub gas: GasMeter,
/// Contract storage.
pub storage: S,
/// Return data from last call.
pub return_data: Vec<u8>,
/// Logs accumulated.
pub logs: Vec<crate::ContractLog>,
/// Chain ID.
pub chain_id: u64,
}
impl<S: ContractStorage> ExecutionContext<S> {
/// Creates a new execution context.
pub fn new(
block: BlockInfo,
tx: TransactionInfo,
call: CallContext,
gas_limit: u64,
storage: S,
chain_id: u64,
) -> Self {
ExecutionContext {
block,
tx,
call,
gas: GasMeter::new(gas_limit),
storage,
return_data: Vec::new(),
logs: Vec::new(),
chain_id,
}
}
/// Creates a context for testing.
pub fn testing(call: CallContext) -> ExecutionContext<MemoryStorage> {
ExecutionContext {
block: BlockInfo::default(),
tx: TransactionInfo::default(),
call,
gas: GasMeter::new(10_000_000),
storage: MemoryStorage::new(),
return_data: Vec::new(),
logs: Vec::new(),
chain_id: 1,
}
}
/// Gets the current contract address as Address.
pub fn address(&self) -> Address {
// Contract address derived from contract ID hash
Address::from_ed25519_pubkey(Network::Mainnet, self.call.contract.as_bytes())
}
/// Gets the caller address.
pub fn caller(&self) -> Address {
self.call.caller.clone()
}
/// Gets the transaction origin.
pub fn origin(&self) -> Address {
self.tx.origin.clone()
}
/// Gets the call value.
pub fn value(&self) -> u64 {
self.call.value
}
/// Gets the call input data.
pub fn calldata(&self) -> &[u8] {
&self.call.input
}
/// Gets the call data size.
pub fn calldatasize(&self) -> usize {
self.call.input.len()
}
/// Gets remaining gas.
pub fn gas_remaining(&self) -> u64 {
self.gas.remaining()
}
/// Gets the block timestamp.
pub fn timestamp(&self) -> u64 {
self.block.timestamp
}
/// Gets the block height.
pub fn block_height(&self) -> u64 {
self.block.height
}
/// Gets the block hash.
pub fn block_hash(&self) -> Hash256 {
self.block.hash
}
/// Checks if this is a static call.
pub fn is_static(&self) -> bool {
self.call.is_static
}
/// Checks if state modifications are allowed.
pub fn can_modify_state(&self) -> bool {
!self.call.is_static
}
/// Sets the return data.
pub fn set_return_data(&mut self, data: Vec<u8>) {
self.return_data = data;
}
/// Gets the last return data.
pub fn get_return_data(&self) -> &[u8] {
&self.return_data
}
/// Emits a log.
pub fn emit_log(&mut self, topics: Vec<Hash256>, data: Vec<u8>) -> Result<(), crate::VmError> {
if self.is_static() {
return Err(crate::VmError::ExecutionError(
"Cannot emit logs in static call".into(),
));
}
// Charge gas
self.gas
.charge_log(data.len(), topics.len())
.map_err(|_e| crate::VmError::OutOfGas {
used: self.gas.consumed(),
limit: self.gas.limit(),
})?;
self.logs.push(crate::ContractLog {
contract: self.call.contract,
topics,
data,
});
Ok(())
}
/// Creates a nested context for a sub-call.
pub fn nested_call(
&self,
target: ContractId,
value: u64,
input: Vec<u8>,
gas_limit: u64,
) -> Result<ExecutionContext<MemoryStorage>, crate::VmError> {
if self.call.depth >= crate::MAX_CALL_DEPTH {
return Err(crate::VmError::CallDepthExceeded(self.call.depth + 1));
}
let nested_call = self.call.nested(target, value, input);
Ok(ExecutionContext {
block: self.block.clone(),
tx: self.tx.clone(),
call: nested_call,
gas: GasMeter::new(gas_limit),
storage: MemoryStorage::new(), // Nested calls get fresh storage view
return_data: Vec::new(),
logs: Vec::new(),
chain_id: self.chain_id,
})
}
}
/// Builder for execution contexts.
pub struct ContextBuilder<S: ContractStorage = MemoryStorage> {
block: BlockInfo,
tx: TransactionInfo,
call: Option<CallContext>,
gas_limit: u64,
storage: Option<S>,
chain_id: u64,
}
impl<S: ContractStorage> ContextBuilder<S> {
/// Creates a new context builder.
pub fn new() -> Self {
ContextBuilder {
block: BlockInfo::default(),
tx: TransactionInfo::default(),
call: None,
gas_limit: 10_000_000,
storage: None,
chain_id: 1,
}
}
/// Sets block info.
pub fn block(mut self, block: BlockInfo) -> Self {
self.block = block;
self
}
/// Sets transaction info.
pub fn transaction(mut self, tx: TransactionInfo) -> Self {
self.tx = tx;
self
}
/// Sets call context.
pub fn call(mut self, call: CallContext) -> Self {
self.call = Some(call);
self
}
/// Sets gas limit.
pub fn gas_limit(mut self, limit: u64) -> Self {
self.gas_limit = limit;
self
}
/// Sets storage.
pub fn storage(mut self, storage: S) -> Self {
self.storage = Some(storage);
self
}
/// Sets chain ID.
pub fn chain_id(mut self, chain_id: u64) -> Self {
self.chain_id = chain_id;
self
}
/// Builds the context.
pub fn build(self) -> Result<ExecutionContext<S>, &'static str> {
let call = self.call.ok_or("Call context is required")?;
let storage = self.storage.ok_or("Storage is required")?;
Ok(ExecutionContext::new(
self.block,
self.tx,
call,
self.gas_limit,
storage,
self.chain_id,
))
}
}
impl Default for ContextBuilder<MemoryStorage> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::MemoryStorage;
use synor_types::Network;
fn test_contract_id() -> ContractId {
ContractId::from_bytes([0x42; 32])
}
fn test_address() -> Address {
Address::from_ed25519_pubkey(Network::Mainnet, &[0x11; 32])
}
#[test]
fn test_call_context() {
let contract = test_contract_id();
let caller = test_address();
let ctx = CallContext::new(contract, caller, 1000, vec![1, 2, 3]);
assert_eq!(ctx.value, 1000);
assert_eq!(ctx.depth, 0);
assert!(!ctx.is_static);
}
#[test]
fn test_nested_call() {
let contract = test_contract_id();
let caller = test_address();
let ctx = CallContext::new(contract, caller, 1000, vec![]);
let nested = ctx.nested(ContractId::from_bytes([0x99; 32]), 500, vec![4, 5, 6]);
assert_eq!(nested.depth, 1);
assert_eq!(nested.value, 500);
}
#[test]
fn test_execution_context() {
let contract = test_contract_id();
let caller = test_address();
let call = CallContext::new(contract, caller, 1000, vec![1, 2, 3]);
let ctx = ExecutionContext::<MemoryStorage>::testing(call);
assert_eq!(ctx.value(), 1000);
assert_eq!(ctx.calldatasize(), 3);
assert!(ctx.gas_remaining() > 0);
}
#[test]
fn test_static_call() {
let contract = test_contract_id();
let caller = test_address();
let call = CallContext::static_call(contract, caller, vec![]);
let ctx = ExecutionContext::<MemoryStorage>::testing(call);
assert!(ctx.is_static());
assert!(!ctx.can_modify_state());
}
}