Platform: Code4rena
Start Date: 01/03/2024
Pot Size: $60,500 USDC
Total HM: 4
Participants: 18
Period: 21 days
Judge: Lambda
Total Solo HM: 3
Id: 344
League: POLKADOT
Rank: 16/18
Findings: 1
Award: $59.23
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: hunter_w3b
Also found by: 0xepley, Bauchibred, Cryptor, DarkTower, aariiif, albahaca, fouzantanveer, kaveyjoe, popeye, roguereggiant
59.2291 USDC - $59.23
Serial No. | Topic |
---|---|
01 | Overview |
02 | Architecture Overview |
03 | Approach Taken in Evaluating Pink Runtime |
04 | Runtime Components Analysis |
05 | Codebase Quality |
06 | Systematic Risks |
The Pink Runtime is a substrate execution engine for executing smart contracts in the Phala Network. It's generally designed to provide a trustless environment for executing smart contracts - hence confidentiality is primarily built-in. The Phala Protocol through its use of the Rust language developed the runtime building on top of Substrate's pallet-contracts
. Pink's computation is done offchain in a trustless and decentralized environment.
At its core, the protocol employs data interoperability in the sense that Phat contracts deployed on the Phala Network can communicate or be interfaced with smart contracts on any other chain.
During the course of this analysis for the Pink Runtime, we engaged the protocol with a function-trace and deep dive overview of the protocol to dissect each function, logic and core implementation. These points are talked about in the Runtime Components Analysis section of this report.
Flaws of functions we covered in the Runtime Component Analysis section is available as individual components for such functions in the Systemic risk sections along with insights to make the logic better.
Before we delve in, it is crucial to understand what a runtime is in the substrate term. A runtime has all the business logic for executing transactions, transitioning & saving state as well as interacting with other nodes. In essence, it's like the EVM but for executing transactions in another network being Phala. To understand substrate and what the Phala Network is doing, we had to spend some time building a runtime from scratch ourselves because that is how we can truly understand substrate's working under the hood. We spent 7 days looking into the protocol in total with 2-3 hours allocated each day.
We focused mainly in the runtime's initialization, runtime configurations, runtime calls, smart contract instantiation and execution and finally storage within the pink runtime. With the limited time we had to look into this codebase, we spent the most of our time covering the above mentioned logic of the runtime.
D1-2:
D2-4:
D4-7:
#[no_mangle] pub unsafe extern "C" fn __pink_runtime_init( config: *const config_t, ecalls: *mut ecalls_t, ) -> ::core::ffi::c_int
(__pink_runtime_init)
function: This is the entry point for the runtime initialization. It takes two parameters: config (a pointer to configuration data) and ecalls (a pointer to the table of external calls). Firstly, it sets the OCall
function using ocall_impl::set_ocall_fn
. This function handles the initialization of OCall
functions.
Afterwards, it checks if the ecalls
pointer is null
, logs an error if it is, and returns -1
.
It sets the ecall
and get_version
functions in the ecalls
table and if the config.is_dylib
is true
, it initializes the logger
. When all that is done, the initialization function finally returns 0
to indicate a successful initialization.
unsafe extern "C" fn get_version(major: *mut u32, minor: *mut u32) {
(get_version)
function: This function sets the major and minor version numbers using the crate::version()
function.
unsafe extern "C" fn ecall( call_id: u32, data: *const u8, len: usize, ctx: *mut ::core::ffi::c_void, output_fn: output_fn_t, ) {
(ecall)
function: This function serves as a central hub for all ECall
functions.
It takes parameters such as a call_id
(which is a unique identifier for the function call), data
( apointer to the input data), len
(the size of input data), ctx
(the context pointer), and output_fn
(a function pointer to receive the output).
It uses ecall::dispatch
to route function calls to ECallImpl
, which implements the ECalls
trait.
Finally, it invokes the output function to send the results back.
macro_rules! instrument_context { ($context: expr) => {
(instrument_context!)
macro definition: This macro takes a context parameter and is used for instrumentation, such as for tracing or logging. It creates spans for prpc
(for remote procedure calls) and pink
(referring to the Pink Runtime) with additional attributes like blk
for block number and req_id
for request ID.
It utilizes the tracing crate for span creation and its management.
impl Executing for crate::storage::ExternalStorage {
Executing Trait Implementations: The Executing trait is implemented for ExternalStorage
, which is used for executing functions within the context of the Pink Runtime.
fn execute<T>(&self, f: impl FnOnce() -> T) -> T {
The execute
function defined within the Executing
implementation takes a closure and executes it within the context of the Pink Runtime.
It then retrieves the execution context using OCallImpl.exec_context()
and instruments it using the instrument_context!
macro.
fn execute_mut<T>(&mut self, f: impl FnOnce() -> T) -> T
The execute_mut
function defined in the same Executing
implementation behaves similarly to the execute
function, but in this context, it takes a mutable
reference to self i.e Pink Runtime and additionally emits side effects using OCallImpl.emit_side_effects()
.
impl ecall::ECalls for ECallImpl {
This implementation provides various functions to interact with the Pink Runtime such as:
fn cluster_id(&self) -> Hash {
: For retrieval of cluster IDs from the Pink Pallet.fn setup(&mut self, config: ClusterSetupConfig) -> Result<(), String> {
: Setting up the Pink cluster with provided configuration.fn deposit(&mut self, who: AccountId, value: Balance) {
: Depositing funds into an account using the Balances Palletfn set_key(&mut self, key: Sr25519SecretKey) {
: Sets the secret key for the Pink Palletfn get_key(&self) -> Option<Sr25519SecretKey> {
: Retrieves the secret key for the Pink Palletfn upload_code( &mut self, account: AccountId, code: Vec<u8>, deterministic: bool, ) -> Result<Hash, String> {
: Uploads wasm code to the Pink Pallet.fn upload_sidevm_code(&mut self, account: AccountId, code: Vec<u8>) -> Result<Hash, String> {
: Uploads wasm code to the Pink Pallet.
fn contract_instantiate( &mut self, code_hash: Hash, input_data: Vec<u8>, salt: Vec<u8>, mode: ExecutionMode, tx_args: TransactionArguments, ) -> Vec<u8> {
: Executing contract instantiation on the Pink Pallet.fn contract_call( &mut self, address: AccountId, input_data: Vec<u8>, mode: ExecutionMode, tx_args: TransactionArguments, ) -> Vec<u8> {
: Executing contract calls on the Pink Pallet.fn sanitize_args(mut args: TransactionArguments, mode: ExecutionMode) -> TransactionArguments { const GAS_PER_SECOND: u64 = WEIGHT_REF_TIME_PER_SECOND * 5; let gas_limit = match mode { ExecutionMode::Transaction | ExecutionMode::Estimating => GAS_PER_SECOND / 2, ExecutionMode::Query => GAS_PER_SECOND * 10, }; args.gas_limit = args.gas_limit.min(gas_limit); args }
sanitize_args()
function adjusts the gas limit based on the execution mode (Transaction, Estimating, or Query).GAS_PER_SECOND
, which represents the gas consumed per second.fn handle_deposit(args: &TransactionArguments) { if args.deposit > 0 { let _ = PalletBalances::deposit_creating(&args.origin, args.deposit); } }
The handle_deposit()
function handles deposits
specified in the transaction arguments.
If the deposit amount (args.deposit)
is greater than 0
, it deposits the specified amount of funds into the origin account using the deposit_creating
function from the Balances
Pallet.
Errors are handled using Result
types with String
error messages, providing detailed error information in case of failure.
Error messages are logged using the log crate and also sent to the server through OCallImpl.log_to_server
.
static mut OCALL: InnerType<cross_call_fn_t> = _default_ocall;
OCALL
of type InnerType<cross_call_fn_t>
. This variable is used to store the function pointer for handling OCall
operations._default_ocall
, which is a function that panics with a message indicating that no OCall
function is provided.pub(super) fn set_ocall_fn(ocalls: ocalls_t) -> Result<(), &'static str> { let Some(ocall) = ocalls.ocall else { return Err("No ocall function provided"); }; unsafe { OCALL = ocall; #[cfg(feature = "allocator")] if let Some(alloc) = ocalls.alloc { allocator::ALLOC_FUNC = alloc; } #[cfg(feature = "allocator")] if let Some(dealloc) = ocalls.dealloc { allocator::DEALLOC_FUNC = dealloc; } } Ok(()) }
set_ocall_fn()
function sets the OCall
functions by accepting an ocalls_t
structure containing OCall
function pointers.OCall
function from the ocalls
parameter and sets it to the OCALL
variable.pub(crate) struct OCallImpl; impl CrossCallMut for OCallImpl { fn cross_call_mut(&mut self, call_id: u32, data: &[u8]) -> Vec<u8> { self.cross_call(call_id, data) } } impl CrossCall for OCallImpl { fn cross_call(&self, id: u32, data: &[u8]) -> Vec<u8> { unsafe extern "C" fn output_fn(ctx: *mut ::core::ffi::c_void, data: *const u8, len: usize) { let output = &mut *(ctx as *mut Vec<u8>); output.extend_from_slice(std::slice::from_raw_parts(data, len)); } unsafe { let mut output = Vec::new(); let ctx = &mut output as *mut _ as *mut ::core::ffi::c_void; OCALL(id, data.as_ptr(), data.len(), ctx, Some(output_fn)); output } } } impl OCall for OCallImpl {}
OCallImpl
struct is defined to handle OCall
operations.CrossCallMut
, CrossCall
, and OCall
traits.CrossCall
and CrossCallMut
traits define methods for making cross calls (calls from the enclave to outside world) with immutable and mutable references.cross_call
and cross_call_mut
methods use the OCALL
function pointer to execute OCall
operations.The configuration for the Pink runtime is defined using the construct_runtime!
macro, which constructs the runtime by combining various pallets and system modules.
Some of these modules include:
frame_system
: The system module for basic system functionalities.pallet_timestamp
: A timestamp module for managing block timestamps.pallet_balances
: A Balances module for handling token balances and transfers.pallet_insecure_randomness_collective_flip
: Randomness module for generating random numbers.pallet_contracts
: Contracts module for smart contract functionality.pallet_pink
: A custom pallet specific to the Pink Network.PinkRuntime
.Constants include:
NORMAL_DISPATCH_RATIO
: Specifies the ratio of normal dispatch to operational dispatch. It is set to 75%.BlockHashCount
: Which specifies the number of block hashes to keep in storage.RuntimeBlockWeights
: This defines the block weights configuration, including base weight and normal dispatch ratio.ExistentialDeposit
: Specifies the existential deposit required for an account to exist.MaxLocks
, MaxReserves
, MaxHolds
: These define maximum limits for locks, reserves, and holds, respectively.impl pallet_pink::Config for PinkRuntime { type Currency = Balances; }
This implementation:
pallet_pink
.Balances
, indicating that the balances module will be used for currency functionality.impl pallet_balances::Config for PinkRuntime {
The pallet_balances
implementation:
pallet_balances
module, which manages token balances and transfers.Defines various associated types and constants such as the:
Balance
: The balance type used in the pink runtime.DustRemoval
: Placeholder type indicating no dust removal functionality.RuntimeEvent
: Type alias for runtime events.ExistentialDeposit
: Specifies the existential deposit required for an account to exist.AccountStore
: Specifies the storage pallet used to store accounts.WeightInfo
: Specifies the weight information provider.MaxLocks
, MaxReserves
, MaxHolds
: The maximum limits for locks, reserves, and holds.ReserveIdentifier
, FreezeIdentifier
: Such identifiers for reserves and freezes.RuntimeHoldReason
, RuntimeFreezeReason
: Reasons for holding and freezing accounts.MaxFreezes
as ()
indicating no maximum freezes.impl frame_system::Config for PinkRuntime {
The frame_system
implementation provides runtime system functionalities and also defines various types such as:
BaseCallFilter
: Specifies the base call filter.BlockWeights
: Specifies the block weights configuration.Nonce
, Hash
, AccountId
: Types for nonce, hash, and account identifiers.Block
, BlockHashCount
: Types for blocks and block hash count.RuntimeEvent
: Type alias for runtime events.PalletInfo
, AccountData
: Information about pallets and account data.impl pallet_insecure_randomness_collective_flip::Config for PinkRuntime {}
This implementation configures the pallet_insecure_randomness_collective_flip
module, which provides insecure randomness generation.
parameter_types! { pub const MinimumPeriod: u64 = 1; }
Definition of a parameter uint64
datatype MinimumPeriod
with a value of 1.
impl pallet_timestamp::Config for PinkRuntime { type Moment = u64; type OnTimestampSet = (); type MinimumPeriod = MinimumPeriod; type WeightInfo = (); }
This implementation configures the pallet_timestamp
module, which provides timestamp functionalities with some associated type definitions such as:
Moment
: Type for representing moments in time, set to u64
.OnTimestampSet
: Placeholder type indicating no action taken on timestamp set.MinimumPeriod
: Specifies the minimum period between timestamp updates.WeightInfo
: Placeholder type for weight information.impl pallet_contracts::Config for PinkRuntime {
This implementation configures the pallet_contracts
module, which enables smart contract functionalities. It also has the associated types below which we will dissect briefly:
Time
: Specifies the timestamp type used.Randomness
: Specifies the randomness type used.Currency
: Specifies the currency type used.RuntimeEvent
: Type alias for runtime events.RuntimeCall
: Type alias for runtime calls.CallFilter
: Specifies the call filter.CallStack
: Array type representing the call stack.WeightPrice
: Specifies the weight price.WeightInfo
: Specifies the weight information provider.ChainExtension
: Specifies the chain extension used.Schedule
: Specifies the contract execution schedule.DepositPerByte
, DepositPerItem
, DefaultDepositLimit
: Deposit-related parameters.AddressGenerator
: Specifies the address generator.MaxCodeLen
, MaxStorageKeyLen
, MaxDebugBufferLen
: Parameters related to code and storage limits.UnsafeUnstableInterface
: Specifies whether unsafe unstable interface is enabled.Migrations
: Specifies the migration types.CodeHashLockupDepositPercent
: Specifies the deposit percent for code hash lockup.MaxDelegateDependencies
: Specifies the maximum delegate dependencies.RuntimeHoldReason
: Specifies the runtime hold reason.Debug
, Environment
, Xcm
: Placeholder types for debugging, environment, and cross-chain messaging.Let's look into some important type aliases in the contract.rs
file:
EventRecord
: This is an alias for frame_system::EventRecord
specialized for the PinkRuntime configuration. It represents an event record containing information about runtime events relating to contracts.ContractExecResult
: An alias for the result of executing a contract, containing a balance and event records.ContractInstantiateResult
: An alias for the result of instantiating a contract, containing an account ID, balance, and event records.ContractResult<T>
: Lastly, an alias for a contract result, containing either a result of type T
wrapped in Result or a dispatch error, along with a balance and event records for the call.macro_rules! define_mask_fn { ($name: ident, $bits: expr, $typ: ty) => {
The define_mask_fn
, generates a function for masking the lowest bits of a given number. To further understand the logic of this macro, let's break down its components:
macro_rules! define_mask_fn { ... }
: This code block defines a macro named define_mask_fn
using the macro_rules!
syntax, which allows for pattern matching and generation of code based on whatever pattern is provided.($name: ident, $bits: expr, $typ: ty) => { ... }
: This pattern expects three parameters such as:$name
: The name of the function to be called.$bits
: The number of bits in the type $typ
.$typ
: The data-type of the input number (e.g u8
, u16
, u32
, u64
, etc.).The macro function is defined with the provided name ($name
) and type signature. Inside the function body:
v
) is calculated to determine the position of the most significant bit.(min_mask_bits to $bits - 1)
.v
is masked to set the lowest bits to 1.So, in simple terms:
macro
to generate masking functions for different types ($typ
) and specify the number of bits to mask ($bits
) along with the minimum number of bits to mask (min_mask_bits
).Moving on, the define_mask_fn
macro is used to create the functions below:
mask_low_bits64
: This function handles masking the lowest bits of a u64
value.
mask_low_bits128
: While this other function masks the lowest bits of a u128
value.
mask_deposit
: This calculates the minimum masked value based on the deposit per byte (deposit_per_byte
) and a constant minimum masked bytes (MIN_MASKED_BYTES
). The minimum masked value is then used to determine the minimum number of bits to mask. The mask_low_bits128
function is then called with the deposit and the calculated minimum mask bits, and the result is lastly returned.
mask_gas
: This function extracts the ref time component from the given weight and passes it to mask_low_bits64
to mask the lowest bits. The resulting masked ref time is then used to create a new weight object with zero gas fees, and the new weight is returned.
pub fn instantiate( code_hash: Hash, input_data: Vec<u8>, salt: Vec<u8>, mode: ExecutionMode, args: TransactionArguments, ) -> ContractInstantiateResult
The instantiate()
function is crucial. It is used to instantiate a contract with the given code hash, input data, salt, execution mode, and transaction arguments. Here's a our breakdown of its implementation:
Parameters:
code_hash
: The hash of the code that the contract will execute.input_data
: The input data for the contract.salt
: The salt value used for contract address derivation.mode
: The execution mode, which determines how the contract will be executed.args
: Transaction arguments containing information such as the tx origin, transfer amount, gas limit, storage deposit limit, gas-free flag, and deposit (which is not used in this function).Execution:
instantiate()
function extracts relevant fields from the args
parameter as we have covered, such as the origin, transfer amount, gas limit, storage deposit limit, and gas-free flag.Weight
object using the provided gas limit and sets the proof size to u64::MAX
which is the max value of a uint64
datatype.contract_tx
function, passing the origin, gas limit, gas-free flag, and a closure that performs the contract instantiation.Contracts::bare_instantiate
function is called to perform the actual contract instantiation. It uses the various parameters, including the origin, transfer amount, gas limit, storage deposit limit, code (specified by the code hash), input data, salt, debug information, and event collection strategy.log
crate.coarse_grained
function is called to adjust the result before returning it. Otherwise, the result is returned as is - which is a ContractInstantiateResult
that contains information about the result of the contract instantiation.pub fn bare_call( address: AccountId, input_data: Vec<u8>, mode: ExecutionMode, tx_args: TransactionArguments, ) -> ContractExecResult {
The bare_call()
function above takes charge for executing a contract call without deploying a new contract instance. It takes the target contract address to execute, input data for the call, execution mode, and transaction arguments as input parameters. Here's a further breakdown of its call trace:
origin
, transfer
, gas_limit
, gas_free
, and storage_deposit_limit
from the provided tx_args
.gas_limit
into a Weight instance and set the proof size to u64::MAX
.Determinism::Enforced
, otherwise Determinism::Relaxed
.contract_tx
's function, passing the origin, gas limit, gas free flag, and a closure that represents the contract call.fn contract_tx<T>( origin: AccountId, gas_limit: Weight, gas_free: bool, tx_fn: impl FnOnce() -> ContractResult<T>, ) -> ContractResult<T> {
The contract_tx
function on the other hand, is an internal helper function used to handle the execution of contract transactions. It takes the origin account, gas limit, gas-free flag, and a closure representing the transaction function. Here's a further breakdown of its call trace:
PalletPink::pay_for_gas
function.tx_fn
).PalletPink::refund_gas
function.In essence, we can see that the bare_call()
function uses the contract_tx
internal function for executing it's logic during a smart contract call as we can see in the code snippet below:
let result = contract_tx(origin.clone(), gas_limit, gas_free, move || { Contracts::bare_call(
impl<Backend> Storage<Backend> { pub fn new(backend: Backend) -> Self { Self { backend } } }
The above code snippet implements a constructor for the runtime Storage
struct. It allows for easy instantiation of the Storage
struct backend storage for the pink runtime.
impl<Backend> Storage<Backend> where Backend: StorageBackend<Hashing> + AsTrieBackend<Hashing>, { pub fn execute_with<R>( &self, exec_context: &ExecContext, f: impl FnOnce() -> R, ) -> (R, ExecSideEffects, OverlayedChanges<Hashing>) {
as_trie_backend
.OverlayedChanges
struct is created to track changes made to storage.overlay.start_transaction()
.Ext
struct is created with the overlay changes, backend, and no trie root.f
is executed within the context of the externalities using sp_externalities::set_and_run_with_externalities
.Hence, within the closure execution:
f
is executed, and its result is obtained.crate::runtime::get_side_effects
.maybe_emit_system_event_block
.overlay.commit_transaction()
pub fn execute_mut<R>( &mut self, context: &ExecContext, f: impl FnOnce() -> R, ) -> (R, ExecSideEffects) { let (rv, effects, overlay) = self.execute_with(context, f); self.commit_changes(overlay); (rv, effects) }
execute_mut
function:
This function is similar to execute_with
but commits the storage changes to the backend. It takes two parameters:
context
: A reference to an ExecContext
object, providing the execution context.f
: A closure that takes no arguments and returns a value of type R
-> Result.f
and the execution side effects.execute_with
is called to execute the closure and obtain the result, effects, and overlay changes.commit_changes
.pub fn changes_transaction( &self, changes: OverlayedChanges<Hashing>, ) -> (Hash, BackendTransaction<Hashing>) { let delta = changes .changes() .map(|(k, v)| (&k[..], v.value().map(|v| &v[..]))); let child_delta = changes.children().map(|(changes, info)| { ( info, changes.map(|(k, v)| (&k[..], v.value().map(|v| &v[..]))), ) }); self.backend .full_storage_root(delta, child_delta, sp_core::storage::StateVersion::V0) }
full_storage_root
method to obtain the root hash and transaction.pub fn commit_changes(&mut self, changes: OverlayedChanges<Hashing>) { let (root, transaction) = self.changes_transaction(changes); self.backend.commit_transaction(root, transaction) }
changes_transaction
to obtain the root hash and transaction.commit_transaction
method to commit the changes.pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> { self.backend .as_trie_backend() .storage(key) .expect("Failed to get storage key") }
Option<Vec<u8>>
, representing the stored value associated with the key
, if it is present.pub fn maybe_emit_system_event_block(events: SystemEvents) { use pink_capi::v1::ocall::OCalls; if events.is_empty() { return; } if !OCallImpl.exec_context().mode.is_transaction() { return; } let body = EventsBlockBody { phala_block_number: System::block_number(), contract_call_nonce: OCallImpl.contract_call_nonce(), entry_contract: OCallImpl.entry_contract(), origin: OCallImpl.origin(), events, }; let number = PalletPink::take_next_event_block_number(); let header = EventsBlockHeader { parent_hash: PalletPink::last_event_block_hash(), number, runtime_version: crate::version(), body_hash: body.using_encoded(sp_core::hashing::blake2_256).into(), }; let header_hash = sp_core::hashing::blake2_256(&header.encode()).into(); PalletPink::set_last_event_block_hash(header_hash); let block = EventsBlock { header, body }; OCallImpl.emit_system_event_block(number, block.encode()); }
maybe_emit_system_event_block()
function first checks if the provided events is empty. If it is, the function returns early, indicating that there are no events to emit.OCallImpl
, is a transaction mode. If it's not, the function returns early, as it only emits events during transactions.EventsBlockBody
instance.The EventsBlockBody
contains such various fields:
phala_block_number
: The block number obtained from the System pallet.contract_call_nonce
: The contract call nonce obtained from the OCallImpl
.entry_contract
: The entry contract obtained from the OCallImpl
.origin
: The origin of the transaction obtained from the OCallImpl
.The function then generates a new event block number by calling PalletPink::take_next_event_block_number
.
It constructs an EventsBlockHeader
instance, containing:
parent_hash
: The hash of the last event block obtained from PalletPink::last_event_block_hash
.number
: The newly generated event block number.runtime_version
: The version of the runtime obtained from crate::version()
.body_hash
: The hash of the encoded EventsBlockBody.The function sets the hash of the last event block to the hash of the current header.
Finally, it constructs an EventsBlock
instance with the header and body, and emits this block using the OCallImpl.emit_system_event_block
function.
Here are some of our observations of the Pink Runtime
codebase's quality.
The runtime is well-formatted and follows consistent naming conventions, which contributes to readability. There are some few comments and documentation are present at various levels of the code, aiding in understanding the purpose and behavior of functions, logic and macros.
The use of descriptive variable and function names makes the code easier to understand.
The runtime is organized into modules, structs, and traits, which helps in maintaining a clear structure and separation of concerns. Macros are used effectively to reduce code duplication and improve maintainability.
The team have tried to adhere to best practices and use efficient algorithmic calculations where applicable. The use of macros
for repetitive tasks improves the runtime performance by reducing function call overhead.
Error handling is implemented using Rust's Result
type, which provides robustness by explicitly handling potential errors. Unsafe code blocks are used judiciously, and precautions are taken to ensure memory safety and prevent undefined behaviors.
This section of the analysis report covers error handling, slight flaws and major systemic risks of the covered components in the Pink Runtime.
/capi/mod.rs
ecalls
is null before usage, there may be scenarios where null pointers are not handled properly elsewhere, leading to potential null pointer de-reference issues.__pink_runtime_init
: While errors are logged, returning a fixed error code (-1)
might not provide enough context to the caller about the nature of the error. This can be improved by returning different error codes based on the type of error encountered.Ocall
: Errors encountered during OCall
setup (set_ocall_fn)
are logged but not propagated further. Depending on how critical these errors are, it would be beneficial to propagate them up the call stack for higher-level error handling./capi/ocall_impl.rs
OCall
function that panics may not be suitable for all scenarios. A more graceful error handling mechanism should be considered./capi/mod.rs
OCall
functions and sets up the ECall
dispatcher mechanism./capi/ecall_impl.rs
instrument_context!
macro is used for adding instrumentation to track the execution context, particularly for tracing or logging purposes. It provides visibility into the execution flow within the Pink Runtime.Executing
trait implementation for ExternalStorage
enables this struct to be involved in executing functions within the runtime related to storage operations.(FnOnce() -> T)
allows for flexible execution of code within the context of the Pink Runtime.ECalls
trait provides a modular interface for interacting with the Pink Runtime, covering various aspects such as cluster setup, contract deployments, key management, and lifecycle events.on_genesis
, on_runtime_upgrade
, and on_idle
ensure proper initialization and maintenance of the Pink Runtime.sanitize_args
function ensures that the gas limit is within reasonable bounds based on the execution mode. This prevents excessive gas consumption and potential DoS attacks.handle_deposit
function ensures that deposits specified in transaction arguments are properly handled, ensuring that funds are deposited into the origin account if required and gatekeeps against zero value deposits./capi/ocall_impl.rs
OCall
functions dynamically, allowing flexibility in handling outgoing enclave calls.runtime.rs
(pallet_pink)
and standard FRAME
pallets (pallet_balances, frame_system, pallet_insecure_randomness_collective_flip)
.Overall, the Pink Runtime codebase review was a great one to delve into even though this is our first attempt at a Rust codebase after having studied Rust for a couple days.
21 hours
#0 - c4-pre-sort
2024-03-25T07:14:38Z
141345 marked the issue as insufficient quality report
#1 - c4-judge
2024-03-27T18:20:40Z
OpenCoreCH marked the issue as grade-b