use super::*;
const STORAGE_PUT_PER_BYTE_GAS: u64 = 100_000;
const CHECKMULTISIG_PER_VERIFY_GAS: u64 = 1_000;
const CRYPTOLIB_CONTRACT_HASH: [u8; 20] =
*b"\x1b\xf5\x75\xab\x11\x89\x68\x84\x13\x61\x0a\x35\xa1\x28\x86\xcd\xe0\xb6\x6c\x72";
impl ExecutionContext {
fn syscall_extra_input_gas(&self, syscall_id: [u8; 4]) -> u64 {
let Some(name) = spec::syscall_name(&syscall_id) else {
return 0;
};
match name {
"System.Storage.Put" => {
let depth = self.stack.len();
if depth < 3 {
return 0;
}
let key_len = stack_item_byte_len(&self.stack[depth - 2]) as u64;
let value_len = stack_item_byte_len(&self.stack[depth - 3]) as u64;
key_len
.saturating_add(value_len)
.saturating_mul(STORAGE_PUT_PER_BYTE_GAS)
}
"System.Crypto.CheckMultisig" => {
let depth = self.stack.len();
if depth < 2 {
return 0;
}
let sig_count = stack_item_array_len(&self.stack[depth - 1]) as u64;
let pub_count = stack_item_array_len(&self.stack[depth - 2]) as u64;
sig_count
.saturating_mul(pub_count)
.saturating_mul(CHECKMULTISIG_PER_VERIFY_GAS)
}
"System.Contract.Call" => {
let depth = self.stack.len();
if depth < 4 {
return 0;
}
let hash_item = &self.stack[depth - 1];
let method_item = &self.stack[depth - 2];
let args_item = &self.stack[depth - 4];
let hash_matches_cryptolib = match hash_item {
StackItem::ByteArray(bytes) => {
let b = bytes.borrow();
b.len() == 20 && b.as_slice() == &CRYPTOLIB_CONTRACT_HASH[..]
}
_ => false,
};
if !hash_matches_cryptolib {
return 0;
}
let method_bytes = match method_item {
StackItem::ByteArray(b) => b.borrow().clone(),
_ => return 0,
};
let Ok(method_str) = std::str::from_utf8(&method_bytes) else {
return 0;
};
if !is_cryptolib_hash_method(method_str) {
return 0;
}
let input_bytes = match args_item {
StackItem::Array(items) => {
items.borrow().first().map(stack_item_byte_len).unwrap_or(0)
}
other => stack_item_byte_len(other),
};
(input_bytes as u64).saturating_mul(HASH_PER_BYTE_GAS)
}
_ => 0,
}
}
pub(crate) fn execute_syscall_instruction(&mut self, opcode: u8) -> Result<bool, RuntimeError> {
if opcode != 0x41 {
return Ok(false);
}
if self.instruction_pointer + 4 >= self.bytecode.len() as u32 {
return Err(RuntimeError::ExecutionError {
message: "SYSCALL: insufficient bytecode".to_string(),
});
}
let mut syscall_id = [0u8; 4];
syscall_id.copy_from_slice(
&self.bytecode
[self.instruction_pointer as usize + 1..self.instruction_pointer as usize + 5],
);
if let Some(extra_cost) = self.syscall_gas.get(&syscall_id) {
let extra_input = self.syscall_extra_input_gas(syscall_id);
let total_extra = match extra_cost.checked_add(extra_input) {
Some(t) => t,
None => {
return Err(RuntimeError::OutOfGas {
used: u64::MAX,
limit: self.gas_limit,
});
}
};
let projected = match self.gas_used.checked_add(total_extra) {
Some(p) => p,
None => {
return Err(RuntimeError::OutOfGas {
used: u64::MAX,
limit: self.gas_limit,
});
}
};
if projected > self.gas_limit {
return Err(RuntimeError::OutOfGas {
used: projected,
limit: self.gas_limit,
});
}
self.gas_used = projected;
}
self.handle_syscall(syscall_id)?;
if self.syscall_suppress_ip_advance {
self.syscall_suppress_ip_advance = false;
} else {
self.instruction_pointer += 5;
}
Ok(true)
}
}
fn stack_item_byte_len(item: &StackItem) -> usize {
match item {
StackItem::ByteArray(bytes) => bytes.borrow().len(),
StackItem::Integer(_) | StackItem::UnsignedInteger(_) => 8,
StackItem::Boolean(_) => 1,
StackItem::Null => 0,
StackItem::Array(items) => items.borrow().len().max(32),
StackItem::Map(map) => map.borrow().len().max(32),
}
}
fn stack_item_array_len(item: &StackItem) -> usize {
match item {
StackItem::Array(items) => items.borrow().len(),
_ => 1,
}
}
fn is_cryptolib_hash_method(method: &str) -> bool {
let lower = method.to_ascii_lowercase();
matches!(
lower.as_str(),
"sha256" | "keccak256" | "ripemd160" | "sha1" | "murmur32"
)
}