solana-syscalls 4.1.0

Solana syscalls.
Documentation
use {
    super::*, crate::translate_mut,
    solana_program_runtime::execution_budget::SVMTransactionExecutionCost, solana_sbpf::ebpf,
};

fn get_sysvar<T: std::fmt::Debug + SysvarSerialize + Clone>(
    sysvar: Result<Arc<T>, InstructionError>,
    var_addr: u64,
    invoke_context: &mut InvokeContext,
) -> Result<u64, Error> {
    let amount = invoke_context
        .get_execution_cost()
        .sysvar_base_cost
        .saturating_add(size_of::<T>() as u64);
    invoke_context.compute_meter.consume_checked(amount)?;

    // If a test case contains a program that is owned by the deprecated
    // bpf loader but also contains get_sysvar syscalls, the store into the
    // host memory will segfault due to unaligned accesses. However, this has
    // no consensus impact because this loader was deprecated before the get_sysvar
    // syscall was activated.
    let check_aligned = invoke_context.get_check_aligned();
    if !check_aligned {
        return Err(SyscallError::UnalignedPointer.into());
    }

    if var_addr >= ebpf::MM_INPUT_START
        && invoke_context
            .get_feature_set()
            .syscall_parameter_address_restrictions
    {
        return Err(SyscallError::InvalidPointer.into());
    }

    let memory_mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
    translate_mut!(
        memory_mapping,
        check_aligned,
        let var: &mut T = map(var_addr)?;
    );

    // this clone looks unnecessary now, but it exists to zero out trailing alignment bytes
    // it is unclear whether this should ever matter
    // but there are tests using MemoryMapping that expect to see this
    // we preserve the previous behavior out of an abundance of caution
    let sysvar: Arc<T> = sysvar?;
    *var = T::clone(sysvar.as_ref());

    Ok(SUCCESS)
}

declare_builtin_function!(
    /// Get a Clock sysvar
    SyscallGetClockSysvar,
    fn rust(
        invoke_context: &mut InvokeContext<'_, '_>,
        var_addr: u64,
        _arg2: u64,
        _arg3: u64,
        _arg4: u64,
        _arg5: u64,
    ) -> Result<u64, Error> {
        get_sysvar(
            invoke_context.environment_config.sysvar_cache().get_clock(),
            var_addr,
            invoke_context,
        )
    }
);

declare_builtin_function!(
    /// Get a EpochSchedule sysvar
    SyscallGetEpochScheduleSysvar,
    fn rust(
        invoke_context: &mut InvokeContext<'_, '_>,
        var_addr: u64,
        _arg2: u64,
        _arg3: u64,
        _arg4: u64,
        _arg5: u64,
    ) -> Result<u64, Error> {
        get_sysvar(
            invoke_context.environment_config.sysvar_cache().get_epoch_schedule(),
            var_addr,
            invoke_context,
        )
    }
);

declare_builtin_function!(
    /// Get a EpochRewards sysvar
    SyscallGetEpochRewardsSysvar,
    fn rust(
        invoke_context: &mut InvokeContext<'_, '_>,
        var_addr: u64,
        _arg2: u64,
        _arg3: u64,
        _arg4: u64,
        _arg5: u64,
    ) -> Result<u64, Error> {
        get_sysvar(
            invoke_context.environment_config.sysvar_cache().get_epoch_rewards(),
            var_addr,
            invoke_context,
        )
    }
);

declare_builtin_function!(
    /// Get a Fees sysvar
    SyscallGetFeesSysvar,
    fn rust(
        invoke_context: &mut InvokeContext<'_, '_>,
        var_addr: u64,
        _arg2: u64,
        _arg3: u64,
        _arg4: u64,
        _arg5: u64,
    ) -> Result<u64, Error> {
        #[expect(deprecated)]
        {
            get_sysvar(
                invoke_context.environment_config.sysvar_cache().get_fees(),
                var_addr,
                invoke_context,
            )
        }
    }
);

declare_builtin_function!(
    /// Get a Rent sysvar
    SyscallGetRentSysvar,
    fn rust(
        invoke_context: &mut InvokeContext<'_, '_>,
        var_addr: u64,
        _arg2: u64,
        _arg3: u64,
        _arg4: u64,
        _arg5: u64,
    ) -> Result<u64, Error> {
        get_sysvar(
            invoke_context.environment_config.sysvar_cache().get_rent(),
            var_addr,
            invoke_context,
        )
    }
);

declare_builtin_function!(
    /// Get a Last Restart Slot sysvar
    SyscallGetLastRestartSlotSysvar,
    fn rust(
        invoke_context: &mut InvokeContext<'_, '_>,
        var_addr: u64,
        _arg2: u64,
        _arg3: u64,
        _arg4: u64,
        _arg5: u64,
    ) -> Result<u64, Error> {
        get_sysvar(
            invoke_context.environment_config.sysvar_cache().get_last_restart_slot(),
            var_addr,
            invoke_context,
        )
    }
);

const SYSVAR_NOT_FOUND: u64 = 2;
const OFFSET_LENGTH_EXCEEDS_SYSVAR: u64 = 1;

// quoted language from SIMD0127
// because this syscall can both return error codes and abort, well-ordered error checking is crucial
declare_builtin_function!(
    /// Get a slice of a Sysvar in-memory representation
    SyscallGetSysvar,
    fn rust(
        invoke_context: &mut InvokeContext<'_, '_>,
        sysvar_id_addr: u64,
        var_addr: u64,
        offset: u64,
        length: u64,
        _arg5: u64,
    ) -> Result<u64, Error> {
        let SVMTransactionExecutionCost {
            sysvar_base_cost,
            cpi_bytes_per_unit,
            mem_op_base_cost,
            ..
        } = *invoke_context.get_execution_cost();

        // Abort: "Compute budget is exceeded."
        let sysvar_id_cost = 32_u64.checked_div(cpi_bytes_per_unit).unwrap_or(0);
        let sysvar_buf_cost = length.checked_div(cpi_bytes_per_unit).unwrap_or(0);
        let cost = sysvar_base_cost
            .saturating_add(sysvar_id_cost)
            .saturating_add(std::cmp::max(sysvar_buf_cost, mem_op_base_cost));
        invoke_context.compute_meter.consume_checked(cost)?;

        // If a test case contains a program that is owned by the deprecated
        // bpf loader but also contains get_sysvar syscalls, the store into the
        // host memory will segfault due to unaligned accesses. However, this has
        // no consensus impact because this loader was deprecated before the get_sysvar
        // syscall was activated.
        let check_aligned = invoke_context.get_check_aligned();
        if !check_aligned {
            return Err(SyscallError::UnalignedPointer.into());
        }

        if var_addr >= ebpf::MM_INPUT_START
            && invoke_context
                .get_feature_set()
                .syscall_parameter_address_restrictions
        {
            return Err(SyscallError::InvalidPointer.into());
        }

        let memory_mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
        // Abort: "Not all bytes in VM memory range `[var_addr, var_addr + length)` are writable."
        translate_mut!(
            memory_mapping,
            check_aligned,
            let var: &mut [u8] = map(var_addr, length)?;
        );

        // Abort: "Not all bytes in VM memory range `[sysvar_id, sysvar_id + 32)` are readable."
        let sysvar_id = translate_type::<Pubkey>(memory_mapping, sysvar_id_addr, check_aligned)?;

        // Abort: "`offset + length` is not in `[0, 2^64)`."
        let offset_length = offset
            .checked_add(length)
            .ok_or(InstructionError::ArithmeticOverflow)?;

        // Abort: "`var_addr + length` is not in `[0, 2^64)`."
        let _ = var_addr
            .checked_add(length)
            .ok_or(InstructionError::ArithmeticOverflow)?;


        // "`2` if the sysvar data is not present in the Sysvar Cache."
        let cache = invoke_context.environment_config.sysvar_cache();
        let Some(sysvar_buf) = cache.sysvar_id_to_buffer(sysvar_id) else {
            return Ok(SYSVAR_NOT_FOUND)
        };

        // "`1` if `offset + length` is greater than the length of the sysvar data."
        if let Some(sysvar_slice) = sysvar_buf.get(offset as usize..offset_length as usize) {
            var.copy_from_slice(sysvar_slice);
        } else {
            return Ok(OFFSET_LENGTH_EXCEEDS_SYSVAR);
        }

        Ok(SUCCESS)
    }
);