pub mod abi;
pub mod constants;
pub mod interface;
pub mod storage;
pub mod types;
pub mod write;
pub use storage::STAKING_ADDRESS;
pub use types::{Delegator, EpochInfo, ListNode, Validator, WithdrawalRequest};
use abi::gas;
use alloc::{
format,
string::{String, ToString},
vec,
vec::Vec,
};
use alloy_sol_types::SolCall;
use interface::IMonadStaking::*;
use revm::{
context_interface::{ContextTr, JournalTr, LocalContextTr},
interpreter::{CallInputs, CallScheme, Gas, InstructionResult, InterpreterResult},
precompile::PrecompileHalt,
primitives::{Address, Bytes, Log, U256},
};
use storage::{
delegator_key, delegator_offsets, global_slots, validator_key, validator_offsets, valset_slots,
withdrawal_key, withdrawal_offsets,
};
pub fn run_staking_precompile<CTX: ContextTr>(
context: &mut CTX,
inputs: &CallInputs,
) -> Result<Option<InterpreterResult>, String> {
if inputs.bytecode_address != STAKING_ADDRESS {
return Ok(None);
}
let is_delegated_call =
inputs.scheme == CallScheme::Call && inputs.target_address != inputs.bytecode_address;
if is_delegated_call {
return Ok(Some(InterpreterResult {
result: InstructionResult::Revert,
gas: Gas::new_spent_with_reservoir(inputs.gas_limit, inputs.reservoir),
output: Bytes::new(),
}));
}
if inputs.scheme != CallScheme::Call || inputs.is_static {
return Ok(Some(InterpreterResult {
result: InstructionResult::Revert,
gas: Gas::new_spent_with_reservoir(inputs.gas_limit, inputs.reservoir),
output: Bytes::new(),
}));
}
let input_bytes: Vec<u8> = match &inputs.input {
revm::interpreter::CallInput::SharedBuffer(range) => context
.local()
.shared_memory_buffer_slice(range.clone())
.map(|slice| slice.to_vec())
.unwrap_or_default(),
revm::interpreter::CallInput::Bytes(bytes) => bytes.0.to_vec(),
};
let selector: [u8; 4] = match input_bytes.get(..4).and_then(|s| s.try_into().ok()) {
Some(s) => s,
None => return Ok(Some(reader_fallback_result(inputs.gas_limit))),
};
let call_value = inputs.call_value();
if write::is_write_selector(selector) {
let caller = inputs.caller;
let mut storage = ContextTrStorage { context };
let result = write::run_staking_write(
&input_bytes,
inputs.gas_limit,
&mut storage,
&caller,
call_value,
)?;
return Ok(Some(result));
}
let result = match selector {
getEpochCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_epoch(context, &input_bytes, inputs.gas_limit)),
getProposerValIdCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_proposer_val_id(context, &input_bytes, inputs.gas_limit)),
getValidatorCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_validator(context, &input_bytes, inputs.gas_limit)),
getDelegatorCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_delegator(context, &input_bytes, inputs.gas_limit)),
getWithdrawalRequestCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_withdrawal_request(context, &input_bytes, inputs.gas_limit)),
getConsensusValidatorSetCall::SELECTOR => {
function_not_payable(&call_value).and_then(|_| {
handle_get_consensus_validator_set(context, &input_bytes, inputs.gas_limit)
})
}
getSnapshotValidatorSetCall::SELECTOR => function_not_payable(&call_value).and_then(|_| {
handle_get_snapshot_validator_set(context, &input_bytes, inputs.gas_limit)
}),
getExecutionValidatorSetCall::SELECTOR => {
function_not_payable(&call_value).and_then(|_| {
handle_get_execution_validator_set(context, &input_bytes, inputs.gas_limit)
})
}
getDelegationsCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_delegations(context, &input_bytes, inputs.gas_limit)),
getDelegatorsCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_delegators(context, &input_bytes, inputs.gas_limit)),
_ => return Ok(Some(reader_fallback_result(inputs.gas_limit))),
};
match result {
Ok((gas_used, output)) => {
let mut interpreter_result = InterpreterResult {
result: InstructionResult::Return,
gas: Gas::new(inputs.gas_limit),
output,
};
if !interpreter_result.gas.record_regular_cost(gas_used) {
interpreter_result.result = InstructionResult::PrecompileOOG;
}
Ok(Some(interpreter_result))
}
Err(e) => {
let mut gas = Gas::new(inputs.gas_limit);
let _ = gas.record_regular_cost(inputs.gas_limit);
Ok(Some(InterpreterResult {
result: if e.is_oog() {
InstructionResult::PrecompileOOG
} else {
InstructionResult::Revert
},
gas,
output: Bytes::copy_from_slice(e.to_string().as_bytes()),
}))
}
}
}
fn handle_get_epoch<CTX: ContextTr>(
context: &mut CTX,
_input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_EPOCH {
return Err(PrecompileHalt::OutOfGas);
}
let epoch_info = read_epoch_info(context)?;
let encoded = getEpochCall::abi_encode_returns(&getEpochReturn {
epoch: epoch_info.epoch,
inEpochDelayPeriod: epoch_info.in_delay_period,
});
Ok((gas::GET_EPOCH, encoded.into()))
}
fn handle_get_proposer_val_id<CTX: ContextTr>(
context: &mut CTX,
_input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_PROPOSER_VAL_ID {
return Err(PrecompileHalt::OutOfGas);
}
let val_id = read_storage_u64(context, global_slots::PROPOSER_VAL_ID)?;
let encoded = getProposerValIdCall::abi_encode_returns(&val_id);
Ok((gas::GET_PROPOSER_VAL_ID, encoded.into()))
}
fn handle_get_validator<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_VALIDATOR {
return Err(PrecompileHalt::OutOfGas);
}
let call = getValidatorCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let val_id = call.validatorId;
let validator = read_validator(context, val_id)?;
let consensus_stake = read_storage_u256(context, storage::consensus_view_key(val_id, 0))?;
let consensus_commission = read_storage_u256(context, storage::consensus_view_key(val_id, 1))?;
let snapshot_stake = read_storage_u256(context, storage::snapshot_view_key(val_id, 0))?;
let snapshot_commission = read_storage_u256(context, storage::snapshot_view_key(val_id, 1))?;
let encoded = getValidatorCall::abi_encode_returns(&getValidatorReturn {
authAddress: validator.auth_address,
flags: validator.flags,
stake: validator.stake,
accRewardPerToken: validator.accumulated_reward_per_token,
commission: validator.commission,
unclaimedRewards: validator.unclaimed_rewards,
consensusStake: consensus_stake,
consensusCommission: consensus_commission,
snapshotStake: snapshot_stake,
snapshotCommission: snapshot_commission,
secpPubkey: Bytes::copy_from_slice(&validator.secp_pubkey),
blsPubkey: Bytes::copy_from_slice(&validator.bls_pubkey),
});
Ok((gas::GET_VALIDATOR, encoded.into()))
}
fn handle_get_delegator<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_DELEGATOR {
return Err(PrecompileHalt::OutOfGas);
}
let call = getDelegatorCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let delegator = read_delegator(context, call.validatorId, &call.delegator)?;
let encoded = getDelegatorCall::abi_encode_returns(&getDelegatorReturn {
stake: delegator.stake,
accRewardPerToken: delegator.accumulated_reward_per_token,
unclaimedRewards: delegator.rewards,
deltaStake: delegator.delta_stake,
nextDeltaStake: delegator.next_delta_stake,
deltaEpoch: delegator.delta_epoch,
nextDeltaEpoch: delegator.next_delta_epoch,
});
Ok((gas::GET_DELEGATOR, encoded.into()))
}
fn handle_get_withdrawal_request<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_WITHDRAWAL_REQUEST {
return Err(PrecompileHalt::OutOfGas);
}
let call = getWithdrawalRequestCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let request =
read_withdrawal_request(context, call.validatorId, &call.delegator, call.withdrawId)?;
let encoded = getWithdrawalRequestCall::abi_encode_returns(&getWithdrawalRequestReturn {
withdrawalAmount: request.amount,
accRewardPerToken: request.accumulator,
withdrawEpoch: request.epoch,
});
Ok((gas::GET_WITHDRAWAL_REQUEST, encoded.into()))
}
const MAX_VALIDATORS_PER_CALL: u32 = 100;
fn handle_get_consensus_validator_set<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
handle_get_validator_set_impl(context, input, gas_limit, valset_slots::CONSENSUS)
}
fn handle_get_snapshot_validator_set<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
handle_get_validator_set_impl(context, input, gas_limit, valset_slots::SNAPSHOT)
}
fn handle_get_execution_validator_set<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
handle_get_validator_set_impl(context, input, gas_limit, valset_slots::EXECUTION)
}
fn handle_get_validator_set_impl<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
base_slot: U256,
) -> Result<(u64, Bytes), PrecompileHalt> {
let gas_cost = gas::GET_CONSENSUS_VALIDATOR_SET;
if gas_limit < gas_cost {
return Err(PrecompileHalt::OutOfGas);
}
let call = getConsensusValidatorSetCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let start_index = call.startIndex;
let length = read_storage_u64(context, base_slot)?;
let start = start_index as u64;
let remaining = length.saturating_sub(start);
let count = remaining.min(MAX_VALIDATORS_PER_CALL as u64) as u32;
let mut val_ids = Vec::with_capacity(count as usize);
for i in 0..count {
let slot = base_slot + U256::from(1 + start_index + i);
let val_id = read_storage_u64(context, slot)?;
val_ids.push(val_id);
}
let next_index = start_index + count;
let is_done = (next_index as u64) >= length;
let encoded =
getConsensusValidatorSetCall::abi_encode_returns(&getConsensusValidatorSetReturn {
isDone: is_done,
nextIndex: next_index,
valIds: val_ids,
});
Ok((gas_cost, encoded.into()))
}
const LINKED_LIST_PAGINATION: u32 = 50;
fn handle_get_delegations<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
let gas_cost = gas::GET_DELEGATIONS;
if gas_limit < gas_cost {
return Err(PrecompileHalt::OutOfGas);
}
let call = getDelegationsCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let (done, next_val_id, val_ids) = traverse_validators_for_delegator(
context,
&call.delegator,
call.startValId,
LINKED_LIST_PAGINATION,
)?;
let encoded = getDelegationsCall::abi_encode_returns(&getDelegationsReturn {
isDone: done,
nextValId: next_val_id,
valIds: val_ids,
});
Ok((gas_cost, encoded.into()))
}
fn handle_get_delegators<CTX: ContextTr>(
context: &mut CTX,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
let gas_cost = gas::GET_DELEGATORS;
if gas_limit < gas_cost {
return Err(PrecompileHalt::OutOfGas);
}
let call = getDelegatorsCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let (done, next_delegator, delegators) = traverse_delegators_for_validator(
context,
call.validatorId,
&call.startDelegator,
LINKED_LIST_PAGINATION,
)?;
let encoded = getDelegatorsCall::abi_encode_returns(&getDelegatorsReturn {
isDone: done,
nextDelegator: next_delegator,
delegators,
});
Ok((gas_cost, encoded.into()))
}
fn read_list_node<CTX: ContextTr>(
context: &mut CTX,
val_id: u64,
delegator_addr: &Address,
) -> Result<ListNode, PrecompileHalt> {
let slot6 = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::LIST_NODE),
)?
.to_be_bytes::<32>();
let slot7 = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::LIST_NODE + 1),
)?
.to_be_bytes::<32>();
Ok(ListNode::from_slots(slot6, slot7))
}
fn traverse_validators_for_delegator<CTX: ContextTr>(
context: &mut CTX,
delegator: &Address,
start_val_id: u64,
limit: u32,
) -> Result<(bool, u64, Vec<u64>), PrecompileHalt> {
let ptr = if start_val_id == 0 {
let sentinel = read_list_node(context, ListNode::SENTINEL_VAL_ID, delegator)?;
sentinel.inext
} else {
start_val_id
};
if ptr == 0 {
return Ok((true, 0, vec![]));
}
let first_node = read_list_node(context, ptr, delegator)?;
if first_node.iprev == 0 {
return Ok((true, ptr, vec![]));
}
let mut results = Vec::with_capacity(limit as usize);
let mut current_ptr = ptr;
let mut current_node = first_node;
let mut count = 0u32;
while current_ptr != 0 && count < limit {
results.push(current_ptr);
let next = current_node.inext;
count += 1;
if next != 0 && count < limit {
current_node = read_list_node(context, next, delegator)?;
}
current_ptr = next;
}
let done = current_ptr == 0;
Ok((done, current_ptr, results))
}
fn traverse_delegators_for_validator<CTX: ContextTr>(
context: &mut CTX,
val_id: u64,
start_delegator: &Address,
limit: u32,
) -> Result<(bool, Address, Vec<Address>), PrecompileHalt> {
let ptr = if *start_delegator == Address::ZERO {
let sentinel = read_list_node(context, val_id, &ListNode::SENTINEL_ADDRESS)?;
sentinel.anext
} else {
*start_delegator
};
if ptr == Address::ZERO {
return Ok((true, Address::ZERO, vec![]));
}
let first_node = read_list_node(context, val_id, &ptr)?;
if first_node.aprev == Address::ZERO {
return Ok((true, ptr, vec![]));
}
let mut results = Vec::with_capacity(limit as usize);
let mut current_ptr = ptr;
let mut current_node = first_node;
let mut count = 0u32;
while current_ptr != Address::ZERO && count < limit {
results.push(current_ptr);
let next = current_node.anext;
count += 1;
if next != Address::ZERO && count < limit {
current_node = read_list_node(context, val_id, &next)?;
}
current_ptr = next;
}
let done = current_ptr == Address::ZERO;
Ok((done, current_ptr, results))
}
fn read_epoch_info<CTX: ContextTr>(context: &mut CTX) -> Result<EpochInfo, PrecompileHalt> {
let epoch = read_storage_u64(context, global_slots::EPOCH)?;
let in_delay_raw = read_storage_u256(context, global_slots::IN_BOUNDARY)?;
let in_delay_period = in_delay_raw != U256::ZERO;
Ok(EpochInfo { epoch, in_delay_period })
}
fn read_validator<CTX: ContextTr>(
context: &mut CTX,
val_id: u64,
) -> Result<Validator, PrecompileHalt> {
let stake = read_storage_u256(context, validator_key(val_id, validator_offsets::STAKE))?;
let accumulated_reward_per_token = read_storage_u256(
context,
validator_key(val_id, validator_offsets::ACCUMULATED_REWARD_PER_TOKEN),
)?;
let commission =
read_storage_u256(context, validator_key(val_id, validator_offsets::COMMISSION))?;
let keys_slot_0 = read_storage_u256(context, validator_key(val_id, validator_offsets::KEYS))?
.to_be_bytes::<32>();
let keys_slot_1 =
read_storage_u256(context, validator_key(val_id, validator_offsets::KEYS + 1))?
.to_be_bytes::<32>();
let keys_slot_2 =
read_storage_u256(context, validator_key(val_id, validator_offsets::KEYS + 2))?
.to_be_bytes::<32>();
let mut keys_concat = [0u8; 96];
keys_concat[0..32].copy_from_slice(&keys_slot_0);
keys_concat[32..64].copy_from_slice(&keys_slot_1);
keys_concat[64..96].copy_from_slice(&keys_slot_2);
let mut secp_pubkey = [0u8; 33];
let mut bls_pubkey = [0u8; 48];
secp_pubkey.copy_from_slice(&keys_concat[0..33]);
bls_pubkey.copy_from_slice(&keys_concat[33..81]);
let address_flags_raw =
read_storage_u256(context, validator_key(val_id, validator_offsets::ADDRESS_FLAGS))?
.to_be_bytes::<32>();
let auth_address = Address::from_slice(&address_flags_raw[0..20]);
let flags = u64::from_be_bytes(address_flags_raw[20..28].try_into().unwrap());
let unclaimed_rewards =
read_storage_u256(context, validator_key(val_id, validator_offsets::UNCLAIMED_REWARDS))?;
Ok(Validator {
stake,
accumulated_reward_per_token,
commission,
secp_pubkey,
bls_pubkey,
auth_address,
flags,
unclaimed_rewards,
})
}
fn read_delegator<CTX: ContextTr>(
context: &mut CTX,
val_id: u64,
delegator_addr: &Address,
) -> Result<Delegator, PrecompileHalt> {
let stake = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::STAKE),
)?;
let accumulated_reward_per_token = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::ACCUMULATED_REWARD_PER_TOKEN),
)?;
let rewards = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::REWARDS),
)?;
let delta_stake = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::DELTA_STAKE),
)?;
let next_delta_stake = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::NEXT_DELTA_STAKE),
)?;
let epochs_raw = read_storage_u256(
context,
delegator_key(val_id, delegator_addr, delegator_offsets::EPOCHS),
)?
.to_be_bytes::<32>();
let delta_epoch = u64::from_be_bytes(epochs_raw[0..8].try_into().unwrap());
let next_delta_epoch = u64::from_be_bytes(epochs_raw[8..16].try_into().unwrap());
Ok(Delegator {
stake,
accumulated_reward_per_token,
rewards,
delta_stake,
next_delta_stake,
delta_epoch,
next_delta_epoch,
})
}
fn read_withdrawal_request<CTX: ContextTr>(
context: &mut CTX,
val_id: u64,
delegator_addr: &Address,
withdrawal_id: u8,
) -> Result<WithdrawalRequest, PrecompileHalt> {
let amount = read_storage_u256(
context,
withdrawal_key(val_id, delegator_addr, withdrawal_id, withdrawal_offsets::AMOUNT),
)?;
let accumulator = read_storage_u256(
context,
withdrawal_key(val_id, delegator_addr, withdrawal_id, withdrawal_offsets::ACCUMULATOR),
)?;
let epoch_raw = read_storage_u256(
context,
withdrawal_key(val_id, delegator_addr, withdrawal_id, withdrawal_offsets::EPOCH),
)?
.to_be_bytes::<32>();
let epoch = u64::from_be_bytes(epoch_raw[0..8].try_into().unwrap());
Ok(WithdrawalRequest { amount, accumulator, epoch })
}
fn read_storage_u256<CTX: ContextTr>(context: &mut CTX, key: U256) -> Result<U256, PrecompileHalt> {
context
.journal_mut()
.sload(STAKING_ADDRESS, key)
.map(|r| r.data)
.map_err(|e| PrecompileHalt::Other(format!("Storage read failed: {e:?}").into()))
}
fn read_storage_u64<CTX: ContextTr>(context: &mut CTX, key: U256) -> Result<u64, PrecompileHalt> {
let value = read_storage_u256(context, key)?;
let bytes = value.to_be_bytes::<32>();
Ok(u64::from_be_bytes(bytes[0..8].try_into().unwrap()))
}
pub trait StorageReader {
fn sload(&mut self, key: U256) -> Result<U256, PrecompileHalt>;
}
struct ContextTrStorage<'a, CTX: ContextTr> {
context: &'a mut CTX,
}
impl<CTX: ContextTr> StorageReader for ContextTrStorage<'_, CTX> {
fn sload(&mut self, key: U256) -> Result<U256, PrecompileHalt> {
self.context
.journal_mut()
.sload(STAKING_ADDRESS, key)
.map(|r| r.data)
.map_err(|e| PrecompileHalt::Other(format!("Storage read failed: {e:?}").into()))
}
}
impl<CTX: ContextTr> write::StakingStorage for ContextTrStorage<'_, CTX> {
fn sstore(&mut self, key: U256, value: U256) -> Result<(), PrecompileHalt> {
self.context
.journal_mut()
.sstore(STAKING_ADDRESS, key, value)
.map(|_| ())
.map_err(|e| PrecompileHalt::Other(format!("Storage write failed: {e:?}").into()))
}
fn transfer(&mut self, from: Address, to: Address, amount: U256) -> Result<(), PrecompileHalt> {
if amount.is_zero() {
return Ok(());
}
match self.context.journal_mut().transfer(from, to, amount) {
Ok(None) => Ok(()),
Ok(Some(e)) => Err(PrecompileHalt::Other(format!("Transfer failed: {e:?}").into())),
Err(e) => Err(PrecompileHalt::Other(format!("Transfer error: {e:?}").into())),
}
}
fn emit_log(&mut self, log: Log) -> Result<(), PrecompileHalt> {
self.context.journal_mut().log(log);
Ok(())
}
}
pub fn run_staking_with_reader<R: StorageReader>(
input: &[u8],
gas_limit: u64,
reader: &mut R,
call_value: U256,
) -> Result<InterpreterResult, String> {
let selector: [u8; 4] = match input.get(..4).and_then(|s| s.try_into().ok()) {
Some(s) => s,
None => return Ok(reader_fallback_result(gas_limit)),
};
let result = match selector {
getEpochCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_epoch_reader(reader, input, gas_limit)),
getProposerValIdCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_proposer_val_id_reader(reader, gas_limit)),
getValidatorCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_validator_reader(reader, input, gas_limit)),
getDelegatorCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_delegator_reader(reader, input, gas_limit)),
getWithdrawalRequestCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_withdrawal_request_reader(reader, input, gas_limit)),
getConsensusValidatorSetCall::SELECTOR => {
function_not_payable(&call_value).and_then(|_| {
handle_get_validator_set_reader(reader, input, gas_limit, valset_slots::CONSENSUS)
})
}
getSnapshotValidatorSetCall::SELECTOR => function_not_payable(&call_value).and_then(|_| {
handle_get_validator_set_reader(reader, input, gas_limit, valset_slots::SNAPSHOT)
}),
getExecutionValidatorSetCall::SELECTOR => {
function_not_payable(&call_value).and_then(|_| {
handle_get_validator_set_reader(reader, input, gas_limit, valset_slots::EXECUTION)
})
}
getDelegationsCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_delegations_reader(reader, input, gas_limit)),
getDelegatorsCall::SELECTOR => function_not_payable(&call_value)
.and_then(|_| handle_get_delegators_reader(reader, input, gas_limit)),
_ => return Ok(reader_fallback_result(gas_limit)),
};
match result {
Ok((gas_used, output)) => {
let mut interpreter_result = InterpreterResult {
result: InstructionResult::Return,
gas: Gas::new(gas_limit),
output,
};
if !interpreter_result.gas.record_regular_cost(gas_used) {
interpreter_result.result = InstructionResult::PrecompileOOG;
}
Ok(interpreter_result)
}
Err(e) => {
let mut gas = Gas::new(gas_limit);
let _ = gas.record_regular_cost(gas_limit);
Ok(InterpreterResult {
result: if e.is_oog() {
InstructionResult::PrecompileOOG
} else {
InstructionResult::Revert
},
gas,
output: Bytes::copy_from_slice(e.to_string().as_bytes()),
})
}
}
}
fn reader_fallback_result(gas_limit: u64) -> InterpreterResult {
if gas_limit < gas::FALLBACK {
return InterpreterResult {
result: InstructionResult::PrecompileOOG,
output: Bytes::new(),
gas: Gas::new(gas_limit),
};
}
let mut gas = Gas::new(gas_limit);
let _ = gas.record_regular_cost(gas_limit);
InterpreterResult {
result: InstructionResult::Revert,
output: Bytes::from("method not supported"),
gas,
}
}
fn function_not_payable(call_value: &U256) -> Result<(), PrecompileHalt> {
if !call_value.is_zero() {
return Err(PrecompileHalt::Other("value non-zero".into()));
}
Ok(())
}
fn handle_get_epoch_reader<R: StorageReader>(
reader: &mut R,
_input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_EPOCH {
return Err(PrecompileHalt::OutOfGas);
}
let epoch_info = read_epoch_info_reader(reader)?;
let encoded = getEpochCall::abi_encode_returns(&getEpochReturn {
epoch: epoch_info.epoch,
inEpochDelayPeriod: epoch_info.in_delay_period,
});
Ok((gas::GET_EPOCH, encoded.into()))
}
fn handle_get_proposer_val_id_reader<R: StorageReader>(
reader: &mut R,
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_PROPOSER_VAL_ID {
return Err(PrecompileHalt::OutOfGas);
}
let val_id = read_storage_u64_reader(reader, global_slots::PROPOSER_VAL_ID)?;
let encoded = getProposerValIdCall::abi_encode_returns(&val_id);
Ok((gas::GET_PROPOSER_VAL_ID, encoded.into()))
}
fn handle_get_validator_reader<R: StorageReader>(
reader: &mut R,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_VALIDATOR {
return Err(PrecompileHalt::OutOfGas);
}
let call = getValidatorCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let val_id = call.validatorId;
let validator = read_validator_reader(reader, val_id)?;
let consensus_stake = read_storage_u256_reader(reader, storage::consensus_view_key(val_id, 0))?;
let consensus_commission =
read_storage_u256_reader(reader, storage::consensus_view_key(val_id, 1))?;
let snapshot_stake = read_storage_u256_reader(reader, storage::snapshot_view_key(val_id, 0))?;
let snapshot_commission =
read_storage_u256_reader(reader, storage::snapshot_view_key(val_id, 1))?;
let encoded = getValidatorCall::abi_encode_returns(&getValidatorReturn {
authAddress: validator.auth_address,
flags: validator.flags,
stake: validator.stake,
accRewardPerToken: validator.accumulated_reward_per_token,
commission: validator.commission,
unclaimedRewards: validator.unclaimed_rewards,
consensusStake: consensus_stake,
consensusCommission: consensus_commission,
snapshotStake: snapshot_stake,
snapshotCommission: snapshot_commission,
secpPubkey: Bytes::copy_from_slice(&validator.secp_pubkey),
blsPubkey: Bytes::copy_from_slice(&validator.bls_pubkey),
});
Ok((gas::GET_VALIDATOR, encoded.into()))
}
fn handle_get_delegator_reader<R: StorageReader>(
reader: &mut R,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_DELEGATOR {
return Err(PrecompileHalt::OutOfGas);
}
let call = getDelegatorCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let delegator = read_delegator_reader(reader, call.validatorId, &call.delegator)?;
let encoded = getDelegatorCall::abi_encode_returns(&getDelegatorReturn {
stake: delegator.stake,
accRewardPerToken: delegator.accumulated_reward_per_token,
unclaimedRewards: delegator.rewards,
deltaStake: delegator.delta_stake,
nextDeltaStake: delegator.next_delta_stake,
deltaEpoch: delegator.delta_epoch,
nextDeltaEpoch: delegator.next_delta_epoch,
});
Ok((gas::GET_DELEGATOR, encoded.into()))
}
fn handle_get_withdrawal_request_reader<R: StorageReader>(
reader: &mut R,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
if gas_limit < gas::GET_WITHDRAWAL_REQUEST {
return Err(PrecompileHalt::OutOfGas);
}
let call = getWithdrawalRequestCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let withdrawal =
read_withdrawal_request_reader(reader, call.validatorId, &call.delegator, call.withdrawId)?;
let encoded = getWithdrawalRequestCall::abi_encode_returns(&getWithdrawalRequestReturn {
withdrawalAmount: withdrawal.amount,
accRewardPerToken: withdrawal.accumulator,
withdrawEpoch: withdrawal.epoch,
});
Ok((gas::GET_WITHDRAWAL_REQUEST, encoded.into()))
}
fn handle_get_validator_set_reader<R: StorageReader>(
reader: &mut R,
input: &[u8],
gas_limit: u64,
base_slot: U256,
) -> Result<(u64, Bytes), PrecompileHalt> {
let gas_cost = gas::GET_CONSENSUS_VALIDATOR_SET;
if gas_limit < gas_cost {
return Err(PrecompileHalt::OutOfGas);
}
let call = getConsensusValidatorSetCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let start_index = call.startIndex;
let length = read_storage_u64_reader(reader, base_slot)?;
let start = start_index as u64;
let remaining = length.saturating_sub(start);
let count = remaining.min(MAX_VALIDATORS_PER_CALL as u64) as u32;
let mut val_ids = Vec::with_capacity(count as usize);
for i in 0..count {
let slot = base_slot + U256::from(1 + start_index + i);
let val_id = read_storage_u64_reader(reader, slot)?;
val_ids.push(val_id);
}
let next_index = start_index + count;
let is_done = (next_index as u64) >= length;
let encoded =
getConsensusValidatorSetCall::abi_encode_returns(&getConsensusValidatorSetReturn {
isDone: is_done,
nextIndex: next_index,
valIds: val_ids,
});
Ok((gas_cost, encoded.into()))
}
fn read_epoch_info_reader<R: StorageReader>(reader: &mut R) -> Result<EpochInfo, PrecompileHalt> {
let epoch = read_storage_u64_reader(reader, global_slots::EPOCH)?;
let in_delay_raw = read_storage_u256_reader(reader, global_slots::IN_BOUNDARY)?;
let in_delay_period = in_delay_raw != U256::ZERO;
Ok(EpochInfo { epoch, in_delay_period })
}
fn read_validator_reader<R: StorageReader>(
reader: &mut R,
val_id: u64,
) -> Result<Validator, PrecompileHalt> {
let stake = read_storage_u256_reader(reader, validator_key(val_id, validator_offsets::STAKE))?;
let accumulated_reward_per_token = read_storage_u256_reader(
reader,
validator_key(val_id, validator_offsets::ACCUMULATED_REWARD_PER_TOKEN),
)?;
let commission =
read_storage_u256_reader(reader, validator_key(val_id, validator_offsets::COMMISSION))?;
let keys_slot_0 =
read_storage_u256_reader(reader, validator_key(val_id, validator_offsets::KEYS))?
.to_be_bytes::<32>();
let keys_slot_1 =
read_storage_u256_reader(reader, validator_key(val_id, validator_offsets::KEYS + 1))?
.to_be_bytes::<32>();
let keys_slot_2 =
read_storage_u256_reader(reader, validator_key(val_id, validator_offsets::KEYS + 2))?
.to_be_bytes::<32>();
let mut keys_concat = [0u8; 96];
keys_concat[0..32].copy_from_slice(&keys_slot_0);
keys_concat[32..64].copy_from_slice(&keys_slot_1);
keys_concat[64..96].copy_from_slice(&keys_slot_2);
let mut secp_pubkey = [0u8; 33];
let mut bls_pubkey = [0u8; 48];
secp_pubkey.copy_from_slice(&keys_concat[0..33]);
bls_pubkey.copy_from_slice(&keys_concat[33..81]);
let address_flags_raw =
read_storage_u256_reader(reader, validator_key(val_id, validator_offsets::ADDRESS_FLAGS))?
.to_be_bytes::<32>();
let auth_address = Address::from_slice(&address_flags_raw[0..20]);
let flags = u64::from_be_bytes(address_flags_raw[20..28].try_into().unwrap());
let unclaimed_rewards = read_storage_u256_reader(
reader,
validator_key(val_id, validator_offsets::UNCLAIMED_REWARDS),
)?;
Ok(Validator {
stake,
accumulated_reward_per_token,
commission,
secp_pubkey,
bls_pubkey,
auth_address,
flags,
unclaimed_rewards,
})
}
fn read_delegator_reader<R: StorageReader>(
reader: &mut R,
val_id: u64,
delegator_addr: &Address,
) -> Result<Delegator, PrecompileHalt> {
let stake = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::STAKE),
)?;
let accumulated_reward_per_token = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::ACCUMULATED_REWARD_PER_TOKEN),
)?;
let rewards = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::REWARDS),
)?;
let delta_stake = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::DELTA_STAKE),
)?;
let next_delta_stake = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::NEXT_DELTA_STAKE),
)?;
let epochs_raw = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::EPOCHS),
)?
.to_be_bytes::<32>();
let delta_epoch = u64::from_be_bytes(epochs_raw[0..8].try_into().unwrap());
let next_delta_epoch = u64::from_be_bytes(epochs_raw[8..16].try_into().unwrap());
Ok(Delegator {
stake,
accumulated_reward_per_token,
rewards,
delta_stake,
next_delta_stake,
delta_epoch,
next_delta_epoch,
})
}
fn read_withdrawal_request_reader<R: StorageReader>(
reader: &mut R,
val_id: u64,
delegator_addr: &Address,
withdrawal_id: u8,
) -> Result<WithdrawalRequest, PrecompileHalt> {
let amount = read_storage_u256_reader(
reader,
withdrawal_key(val_id, delegator_addr, withdrawal_id, withdrawal_offsets::AMOUNT),
)?;
let accumulator = read_storage_u256_reader(
reader,
withdrawal_key(val_id, delegator_addr, withdrawal_id, withdrawal_offsets::ACCUMULATOR),
)?;
let epoch_raw = read_storage_u256_reader(
reader,
withdrawal_key(val_id, delegator_addr, withdrawal_id, withdrawal_offsets::EPOCH),
)?
.to_be_bytes::<32>();
let epoch = u64::from_be_bytes(epoch_raw[0..8].try_into().unwrap());
Ok(WithdrawalRequest { amount, accumulator, epoch })
}
fn read_storage_u256_reader<R: StorageReader>(
reader: &mut R,
key: U256,
) -> Result<U256, PrecompileHalt> {
reader.sload(key)
}
fn read_storage_u64_reader<R: StorageReader>(
reader: &mut R,
key: U256,
) -> Result<u64, PrecompileHalt> {
let value = read_storage_u256_reader(reader, key)?;
let bytes = value.to_be_bytes::<32>();
Ok(u64::from_be_bytes(bytes[0..8].try_into().unwrap()))
}
fn handle_get_delegations_reader<R: StorageReader>(
reader: &mut R,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
let gas_cost = gas::GET_DELEGATIONS;
if gas_limit < gas_cost {
return Err(PrecompileHalt::OutOfGas);
}
let call = getDelegationsCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let (done, next_val_id, val_ids) = traverse_validators_for_delegator_reader(
reader,
&call.delegator,
call.startValId,
LINKED_LIST_PAGINATION,
)?;
let encoded = getDelegationsCall::abi_encode_returns(&getDelegationsReturn {
isDone: done,
nextValId: next_val_id,
valIds: val_ids,
});
Ok((gas_cost, encoded.into()))
}
fn handle_get_delegators_reader<R: StorageReader>(
reader: &mut R,
input: &[u8],
gas_limit: u64,
) -> Result<(u64, Bytes), PrecompileHalt> {
let gas_cost = gas::GET_DELEGATORS;
if gas_limit < gas_cost {
return Err(PrecompileHalt::OutOfGas);
}
let call = getDelegatorsCall::abi_decode_raw(&input[4..])
.map_err(|e| PrecompileHalt::Other(format!("Invalid input: {e}").into()))?;
let (done, next_delegator, delegators) = traverse_delegators_for_validator_reader(
reader,
call.validatorId,
&call.startDelegator,
LINKED_LIST_PAGINATION,
)?;
let encoded = getDelegatorsCall::abi_encode_returns(&getDelegatorsReturn {
isDone: done,
nextDelegator: next_delegator,
delegators,
});
Ok((gas_cost, encoded.into()))
}
fn read_list_node_reader<R: StorageReader>(
reader: &mut R,
val_id: u64,
delegator_addr: &Address,
) -> Result<ListNode, PrecompileHalt> {
let slot6 = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::LIST_NODE),
)?
.to_be_bytes::<32>();
let slot7 = read_storage_u256_reader(
reader,
delegator_key(val_id, delegator_addr, delegator_offsets::LIST_NODE + 1),
)?
.to_be_bytes::<32>();
Ok(ListNode::from_slots(slot6, slot7))
}
fn traverse_validators_for_delegator_reader<R: StorageReader>(
reader: &mut R,
delegator: &Address,
start_val_id: u64,
limit: u32,
) -> Result<(bool, u64, Vec<u64>), PrecompileHalt> {
let ptr = if start_val_id == 0 {
let sentinel = read_list_node_reader(reader, ListNode::SENTINEL_VAL_ID, delegator)?;
sentinel.inext
} else {
start_val_id
};
if ptr == 0 {
return Ok((true, 0, vec![]));
}
let first_node = read_list_node_reader(reader, ptr, delegator)?;
if first_node.iprev == 0 {
return Ok((true, ptr, vec![]));
}
let mut results = Vec::with_capacity(limit as usize);
let mut current_ptr = ptr;
let mut current_node = first_node;
let mut count = 0u32;
while current_ptr != 0 && count < limit {
results.push(current_ptr);
let next = current_node.inext;
count += 1;
if next != 0 && count < limit {
current_node = read_list_node_reader(reader, next, delegator)?;
}
current_ptr = next;
}
let done = current_ptr == 0;
Ok((done, current_ptr, results))
}
fn traverse_delegators_for_validator_reader<R: StorageReader>(
reader: &mut R,
val_id: u64,
start_delegator: &Address,
limit: u32,
) -> Result<(bool, Address, Vec<Address>), PrecompileHalt> {
let ptr = if *start_delegator == Address::ZERO {
let sentinel = read_list_node_reader(reader, val_id, &ListNode::SENTINEL_ADDRESS)?;
sentinel.anext
} else {
*start_delegator
};
if ptr == Address::ZERO {
return Ok((true, Address::ZERO, vec![]));
}
let first_node = read_list_node_reader(reader, val_id, &ptr)?;
if first_node.aprev == Address::ZERO {
return Ok((true, ptr, vec![]));
}
let mut results = Vec::with_capacity(limit as usize);
let mut current_ptr = ptr;
let mut current_node = first_node;
let mut count = 0u32;
while current_ptr != Address::ZERO && count < limit {
results.push(current_ptr);
let next = current_node.anext;
count += 1;
if next != Address::ZERO && count < limit {
current_node = read_list_node_reader(reader, val_id, &next)?;
}
current_ptr = next;
}
let done = current_ptr == Address::ZERO;
Ok((done, current_ptr, results))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DefaultMonad;
use revm::{
bytecode::Bytecode,
database::InMemoryDB,
interpreter::{CallInput, CallValue},
primitives::B256,
};
fn staking_call_inputs_with_input(
scheme: CallScheme,
is_static: bool,
value: CallValue,
input: Bytes,
) -> CallInputs {
CallInputs {
input: CallInput::Bytes(input),
return_memory_offset: 0..0,
gas_limit: 100_000,
reservoir: 0,
bytecode_address: STAKING_ADDRESS,
known_bytecode: (B256::ZERO, Bytecode::default()),
target_address: STAKING_ADDRESS,
caller: Address::ZERO,
value,
scheme,
is_static,
charged_new_account_state_gas: false,
}
}
fn staking_call_inputs(scheme: CallScheme, is_static: bool, value: CallValue) -> CallInputs {
staking_call_inputs_with_input(
scheme,
is_static,
value,
Bytes::from(getEpochCall::SELECTOR.to_vec()),
)
}
#[test]
fn test_staking_address_constant() {
assert_eq!(STAKING_ADDRESS, storage::STAKING_ADDRESS);
}
#[test]
fn test_selectors_match() {
assert_eq!(getEpochCall::SELECTOR, [0x75, 0x79, 0x91, 0xa8]);
assert_eq!(getProposerValIdCall::SELECTOR, [0xfb, 0xac, 0xb0, 0xbe]);
assert_eq!(getValidatorCall::SELECTOR, [0x2b, 0x6d, 0x63, 0x9a]);
assert_eq!(getDelegatorCall::SELECTOR, [0x57, 0x3c, 0x1c, 0xe0]);
assert_eq!(getWithdrawalRequestCall::SELECTOR, [0x56, 0xfa, 0x20, 0x45]);
assert_eq!(getConsensusValidatorSetCall::SELECTOR, [0xfb, 0x29, 0xb7, 0x29]);
assert_eq!(getSnapshotValidatorSetCall::SELECTOR, [0xde, 0x66, 0xa3, 0x68]);
assert_eq!(getExecutionValidatorSetCall::SELECTOR, [0x7c, 0xb0, 0x74, 0xdf]);
}
#[test]
fn test_encode_get_epoch_result() {
let encoded = getEpochCall::abi_encode_returns(&getEpochReturn {
epoch: 100,
inEpochDelayPeriod: true,
});
assert_eq!(encoded.len(), 64);
assert_eq!(&encoded[24..32], &100u64.to_be_bytes());
assert_eq!(encoded[63], 1);
}
#[test]
fn test_delegatecall_rejected() {
let inputs =
staking_call_inputs(CallScheme::DelegateCall, false, CallValue::Transfer(U256::ZERO));
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.gas.remaining(), 0);
assert_eq!(result.gas.total_gas_spent(), inputs.gas_limit);
}
#[test]
fn test_staticcall_rejected() {
let inputs =
staking_call_inputs(CallScheme::StaticCall, false, CallValue::Transfer(U256::ZERO));
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.gas.remaining(), 0);
assert_eq!(result.gas.total_gas_spent(), inputs.gas_limit);
}
#[test]
fn test_callcode_rejected() {
let inputs =
staking_call_inputs(CallScheme::CallCode, false, CallValue::Transfer(U256::ZERO));
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.gas.remaining(), 0);
assert_eq!(result.gas.total_gas_spent(), inputs.gas_limit);
}
#[test]
fn test_call_in_static_context_rejected() {
let inputs = staking_call_inputs(
CallScheme::Call,
true, CallValue::Transfer(U256::ZERO),
);
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.gas.remaining(), 0);
assert_eq!(result.gas.total_gas_spent(), inputs.gas_limit);
}
#[test]
fn test_nonzero_value_rejected() {
let inputs =
staking_call_inputs(CallScheme::Call, false, CallValue::Transfer(U256::from(1)));
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.output, Bytes::from("value non-zero"));
}
#[test]
fn test_unknown_selector_with_nonzero_value_hits_fallback() {
let inputs = staking_call_inputs_with_input(
CallScheme::Call,
false,
CallValue::Transfer(U256::from(1)),
Bytes::from(vec![0xde, 0xad, 0xbe, 0xef]),
);
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.output, Bytes::from("method not supported"));
}
#[test]
fn test_reader_unknown_selector_with_nonzero_value_hits_fallback() {
struct EmptyReader;
impl StorageReader for EmptyReader {
fn sload(&mut self, _key: U256) -> Result<U256, PrecompileHalt> {
Ok(U256::ZERO)
}
}
let mut reader = EmptyReader;
let result =
run_staking_with_reader(&[0xde, 0xad, 0xbe, 0xef], 100_000, &mut reader, U256::from(1))
.expect("reader execution should succeed");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.output, Bytes::from("method not supported"));
}
#[test]
fn test_reader_known_getter_with_nonzero_value_reverts_value_non_zero() {
struct EmptyReader;
impl StorageReader for EmptyReader {
fn sload(&mut self, _key: U256) -> Result<U256, PrecompileHalt> {
Ok(U256::ZERO)
}
}
let mut reader = EmptyReader;
let input = getEpochCall::SELECTOR.to_vec();
let result = run_staking_with_reader(&input, 100_000, &mut reader, U256::from(1))
.expect("reader execution should succeed");
assert_eq!(result.result, InstructionResult::Revert);
assert_eq!(result.output, Bytes::from("value non-zero"));
}
#[test]
fn test_plain_call_accepted() {
use revm::context_interface::JournalTr;
let inputs = staking_call_inputs(CallScheme::Call, false, CallValue::Transfer(U256::ZERO));
let mut ctx = crate::api::default_ctx::MonadContext::monad().with_db(InMemoryDB::default());
ctx.journaled_state.load_account(STAKING_ADDRESS).unwrap();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Return);
}
#[test]
fn test_non_staking_address_returns_none() {
let mut inputs =
staking_call_inputs(CallScheme::Call, false, CallValue::Transfer(U256::ZERO));
inputs.bytecode_address = Address::ZERO;
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
assert!(result.is_none(), "non-staking address should return None");
}
#[test]
fn test_delegated_call_rejected() {
let mut inputs =
staking_call_inputs(CallScheme::Call, false, CallValue::Transfer(U256::ZERO));
inputs.target_address = Address::ZERO;
let mut ctx = crate::api::default_ctx::MonadContext::monad();
let result = run_staking_precompile(&mut ctx, &inputs).unwrap();
let result = result.expect("should return Some for staking address");
assert_eq!(result.result, InstructionResult::Revert);
assert!(result.output.is_empty());
assert_eq!(result.gas.remaining(), 0);
}
}