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: 10/18
Findings: 2
Award: $129.08
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Cryptor
Also found by: 0xTheC0der, Bauchibred, Daniel526, XDZIBECX, ihtishamsudo, zhaojie
69.8535 USDC - $69.85
Issue ID | Description |
---|---|
QA-01 | Unnecessary Gas Refund in Pallet Pink's contract_tx function |
QA-02 | Reconsider hardcoded constants configuration values from the runtime |
QA-03 | Update code and remove testing variables |
QA-04 | Limited Error Handling in ExternalDB's get Function |
QA-05 | Fix typos |
QA-06 | The public TransactionArguments struct should be clearly documented |
contract_tx
functionfn contract_tx<T>( origin: AccountId, gas_limit: Weight, gas_free: bool, tx_fn: impl FnOnce() -> ContractResult<T>, ) -> ContractResult<T> { if !gas_free { if let Err(err) = PalletPink::pay_for_gas(&origin, gas_limit) { return ContractResult { gas_consumed: Weight::zero(), gas_required: Weight::zero(), storage_deposit: Default::default(), debug_message: Default::default(), result: Err(err), events: None, }; } } let result = tx_fn(); //@audit if !gas_free { let refund = gas_limit .checked_sub(&result.gas_consumed) .expect("BUG: consumed gas more than the gas limit"); PalletPink::refund_gas(&origin, refund).expect("BUG: failed to refund gas"); } result }
In this code, if gas_limit
is equal to result.gas_consumed
, the checked_sub
operation will return Some(Weight::zero())
. However, the expect
method will still succeed, and the code will attempt to refund zero gas using PalletPink::refund_gas
.
The current implementation of contract_tx
attempts to refund gas even when the gas consumed by the contract is exactly equal to the gas limit. This can lead to unnecessary calls to the PalletPink::refund_gas
function, potentially impacting performance and code clarity.
Check for zero refund before calling PalletPink::refund_gas
:
if !gas_free { let refund = gas_limit.checked_sub(&result.gas_consumed) .expect("BUG: consumed gas more than the gas limit"); if refund != Weight::zero() { PalletPink::refund_gas(&origin, refund).expect("BUG: failed to refund gas"); } }
This approach explicitly checks if the refund amount is zero before calling PalletPink::refund_gas
. This avoids unnecessary function calls and improves code clarity.
Substrate runtimes, including the provided PinkRuntime
, often use hardcoded constants to define various operational parameters. These include MAX_CODE_LEN
, MaxLocks
, MaxReserves
, and MaxHolds
, which play critical roles in limiting resource usage, ensuring network security, and preventing abuse. However, the static nature of these values can pose challenges to network adaptability and scalability.
Consider the following snippet from the runtime configuration: https://github.com/code-423n4/2024-03-phala-network/blob/a01ffbe992560d8d0f17deadfb9b9a2bed38377e/phala-blockchain/crates/pink/runtime/src/runtime.rs#L114
const MAX_CODE_LEN: u32 = 2 * 1024 * 1024; parameter_types! { pub const MaxLocks: u32 = 50; pub const MaxReserves: u32 = 50; pub const MaxHolds: u32 = 10; }
These parameters directly influence the runtime's behavior, with MAX_CODE_LEN
limiting the maximum allowable size of smart contract code, and MaxLocks
, MaxReserves
, and MaxHolds
dictating the limits on user account operations.
Hardcoding these values means that any adjustments, whether for optimization, security, or feature enhancements, necessitate a runtime upgrade. Runtime upgrades, while supported by Substrate, are non-trivial operations requiring on-chain governance decisions and coordination among network participants. This can lead to situations where necessary adjustments to these parameters are delayed or avoided, potentially impacting network performance, security, and the ability to respond to emerging threats or opportunities.
For example, if the network were to experience a surge in smart contract deployment, the hardcoded MAX_CODE_LEN
might either become a bottleneck for legitimate use cases or, conversely, allow oversized contracts that could strain network resources.
Implement mechanisms that allow these parameters to be adjusted via on-chain governance. This approach enables network stakeholders to propose, vote on, and enact changes to critical parameters without requiring a full runtime upgrade.
#[pallet::storage] pub(super) type MaxCodeLen<T: Config> = StorageValue<_, u32, ValueQuery>;
pub DefaultSchedule: Schedule<PinkRuntime> = { let mut schedule = Schedule::<PinkRuntime>::default(); const MB: u32 = 16; // 64KiB * 16 // Each concurrent query would create a VM instance to serve it. We couldn't // allocate too much here. schedule.limits.memory_pages = 4 * MB; schedule.instruction_weights.base = 8000; schedule.limits.runtime_memory = 2048 * 1024 * 1024; // For unittests @audit schedule.limits.payload_len = 1024 * 1024; // Max size for storage value schedule };
As hinted by the "@audit" sign, there are currently limits thatwere placed due to them being for testing purposes, but project is pending final release and could be released with them.
Bad code structure, deployment with unnecessary variables
Remove unneccessary/unused section of codes before finalizing project.
get
FunctionTake a look at this from the ExternalDB
struct:
impl TrieBackendStorage<Hashing> for ExternalDB { fn get(&self, key: &Hash, _prefix: Prefix) -> Result<Option<DBValue>, DefaultError> { Ok(OCallImpl.storage_get(key.as_ref().to_vec())) } }
The get
function attempts to retrieve data using the OCallImpl.storage_get
function and wraps the result in a Result
. However, it uses the generic DefaultError
type, which doesn't provide specific details about the failure.
The current implementation of the get
function in the ExternalDB
struct returns a Result<Option<DBValue>, DefaultError>
. This approach provides limited information about potential errors during data retrieval using OCalls.
When an error occurs, debuggers or monitoring systems only see a generic "DefaultError". This makes it difficult to pinpoint the exact cause of the issue (e.g., OCall failure, network issues, errors on the host side).
Define a custom error type that captures specific reasons for failures. This could include:
OCallError
: Indicates a general OCall failure.NetworkError
: Indicates a network issue during the OCall.HostError
: Indicates an error reported by the host system.Here's an example with a custom error type:
enum StorageError { OCallError, NetworkError, HostError(String), } impl From<sp_state_machine::DefaultError> for StorageError { fn from(_: sp_state_machine::DefaultError) -> Self { StorageError::OCallError } } impl TrieBackendStorage<Hashing> for ExternalDB { fn get(&self, key: &Hash, _prefix: Prefix) -> Result<Option<DBValue>, StorageError> { OCallImpl.storage_get(key.as_ref().to_vec()) .map_err(|err| match err { // Handle specific OCall errors here (e.g., network issues) _ => StorageError::OCallError, }) } }
This approach provides more context about the error, aiding in debugging and troubleshooting.
/// The priviate key of the cluster #[pallet::storage] #[pallet::getter(fn key)] pub(crate) type Key<T: Config> = StorageValue<_, Sr25519SecretKey>;
Unreadable/Non-understandable code
Change /// The priviate key of the cluster
to:
/// The private key of the cluster
TransactionArguments
struct should be clearly documentedpub struct TransactionArguments { /// The sender's AccountId. pub origin: AccountId, /// The amount of Balance transferred to the contract. pub transfer: Balance, /// The maximum gas amount for this transaction. pub gas_limit: Weight, //@audit /// Indicates whether the transaction requires gas. If `true`, no tokens are reserved for gas. pub gas_free: bool, /// The storage limit for this transaction. `None` indicates no limit. pub storage_deposit_limit: Option<Balance>, /// The balance to deposit to the caller address, used when the caller's balance is insufficient /// for a gas estimation. pub deposit: Balance, }
This struct has 5 implementations and contains the parameters for a contract call or instantiation, case with this is that the dpcumentation around the gas_free
bool is faulty, it currently states that this bool value "Indicates whether the transaction requires gas..." , whereas as the name suggests it should instead be "Indicates whether the transaction does not require gas..." since if the value is true
, no tokens are reserved for gas.
Low, confused docs, make sit harder for users and developers to interact with code
Make changes as suggested in Proof Of Concept.
#0 - c4-pre-sort
2024-03-25T04:58:15Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-03-25T09:06:19Z
18 Bauchibred l r nc 0 0 6
L 1 n L 2 n L 3 n L 4 n L 5 n L 6 n
#2 - c4-judge
2024-03-27T15:31:57Z
OpenCoreCH marked the issue as grade-b
#3 - Bauchibred
2024-03-29T22:30:20Z
Hi @OpenCoreCH, thanks for judging.
I'd like to provide some clarifications on our submissions. Regarding this issue, there seems to be a confusion in the report's title. We intended to describe the function as non-deterministic
rather than deterministic
. The Impact
section discusses the risks of the getRandom()
function consistently returning a predictable value of 0
, which contradicts the expected non-deterministic behavior. Our recommendation aims to ensure true randomness instead of a constant 0
or throwing an error, although the latter is a common practice even in this protocol, it's not being applied in this case.
Also, considering this downgraded issue, I'd like to point out that our overall QA report now contains a total of 7 QAs. We believe this meets the criteria for an A grade, and we kindly request a reassessment of our submission.
#4 - OpenCoreCH
2024-03-30T15:41:26Z
Hi @Bauchibred
Yes I assumed that this should be non-deterministic, but that does not change anything about the judgement. As the sponsor mentioned and as described in the comments (https://github.com/code-423n4/2024-03-phala-network/blob/a01ffbe992560d8d0f17deadfb9b9a2bed38377e/phala-blockchain/crates/pink/pink/src/chain_extension.rs#L397-L422), the intended behaviour is to return an empty vector when this is called in a transaction and it only returns random data if called within a query.
When judging the QA report, I took #15 into consideration. #15 is border-line QA (it is intended behaviour and there is not really anything wrong with that) and the QA report only contains NC's, so I think grade B is appropriate.
🌟 Selected for report: hunter_w3b
Also found by: 0xepley, Bauchibred, Cryptor, DarkTower, aariiif, albahaca, fouzantanveer, kaveyjoe, popeye, roguereggiant
59.2291 USDC - $59.23
Started with a comprehensive walk-through of the provided documentation to understand protocol functionality (heavily based on the pink runtime) and key points, ambiguities were discussed with the sponsors(deva), and possible risk areas were outlined.
Followed by a manual review of each Rust contract in scope, testing function behavior, protocol logic against expectations, and working out potential attack vectors. Comparisons with similar protocols was also performed to identify recurring issues and evaluate fix effectiveness.
Finally, identified issues from the security review were compiled into a comprehensive audit report.
The Pink Runtime serves as the execution engine for ink!
smart contracts within the Phala Network, leveraging the Substrate framework's pallet-contracts
module along with Phala's specialized chain extensions. As a core component of the Phala ecosystem, it facilitates the running of smart contracts on the blockchain. Designed to operate within the secure environment of off-chain TEE (Trusted Execution Environment) workers, the Pink Runtime is instrumental in processing both on-chain transactions and off-chain RPC queries directed towards ink!
contracts. This high-level functionality is encapsulated within a dynamically linked library, libpink.so
, which is compiled for Linux systems and dynamically loaded by the Phala Network's workers to execute smart contracts securely and efficiently.
The architecture of Pink Runtime is distinguished by its dylib interface and an intricate system of cross-calls facilitated through the ecalls/ocalls layer, enabling seamless interactions between the worker environment and the runtime. At its core, the Substrate runtime, complemented by Phala-specific chain extensions, defines the operational foundation of Pink Runtime, ensuring the smooth execution of smart contracts along with the integration of Phala's unique features. This sophisticated structure not only enhances the capability and flexibility of contract execution on Phala Network but also maintains a secure, isolated runtime environment for contracts, thereby safeguarding both contract data and execution logic against external threats and ensuring integrity and confidentiality.
This file is designed to configure and initialize the custom Substrate-based runtime named PinkRuntime
, which includes a set of pallets for various blockchain functionalities such as system operations, timestamp management, balance handling, contract execution, and more.
PinkRuntime
is then constructed using Substrate's construct_runtime!
macro, incorporating essential pallets like frame_system
, pallet_timestamp
, pallet_balances
, pallet_contracts
, and a custom pallet_pink
.parameter_types!
macro is used to define various runtime parameters like BlockHashCount
, RuntimeBlockWeights
, ExistentialDeposit
, MaxCodeLen
, and others that are essential for blockchain operations.on_genesis
, on_runtime_upgrade
, and on_idle
, which are hooks for initializing the runtime, performing migrations or upgrades, and executing tasks when the blockchain is idle.construct_runtime! { pub struct PinkRuntime { System: frame_system, // other pallets... } }
parameter_types! { pub const BlockHashCount: u32 = 250; // other parameters... }
impl pallet_balances::Config for PinkRuntime { type Balance = Balance; // other configurations... }
pub fn on_genesis() { <AllPalletsWithSystem as frame_support::traits::OnGenesis>::on_genesis(); } pub fn on_runtime_upgrade() { // Migration logic... }
#[test] fn check_metadata() { let (major, minor, _) = this_crate::version_tuple!(); let filename = format!("assets/metadata-{major}.{minor}.bin"); check_metadata_with_path(&filename).expect("metadata changed"); }
This file sets up the comprehensive runtime environment for the Substrate-based blockchain, detailing configurations for essential blockchain functionalities. It outlines the process for integrating and configuring pallets, setting runtime parameters, and providing hooks for initializing the runtime and handling upgrades or idle state operations.
This provides the utility functions for instantiating and interacting with the smart contracts in the Substrate-based blockchain environment, specifically within a custom pallet named Pink. It leverages Substrate's pallet_contracts
for contract management and execution.
mask_low_bits64
and mask_low_bits128
are macros used to mask the lower bits of a number, ensuring only a specific number of bits are used for calculations. This is particularly useful for normalizing gas and deposit amounts.mask_deposit
and mask_gas
apply these masking operations to contract deposits and gas weights, respectively, to standardize their values and potentially reduce the granularity of transaction costs.fn mask_deposit(deposit: u128, deposit_per_byte: u128) -> u128 { /* implementation */ } fn mask_gas(weight: Weight) -> Weight { /* implementation */ }
ContractExecResult
, ContractInstantiateResult
, and ContractResult
to encapsulate the results of contract executions, instantiations, and general contract calls. These are specialized versions of pallet_contracts
result types tailored for the Pink pallet.coarse_grained
function takes a ContractResult
and applies masking to its gas and deposit values based on the execution mode. This is done to ensure consistency in how transaction costs are reported and to potentially enhance privacy.fn coarse_grained<T>(result: ContractResult<T>, deposit_per_byte: u128) -> ContractResult<T> { /* implementation */ }
check_instantiate_result
checks the result of a contract instantiation, ensuring it was successful and did not revert.instantiate
attempts to instantiate a new contract with the given code hash, input data, and execution arguments. It applies coarse graining to the result if required by the execution mode.bare_call
performs a raw call to a contract method, similarly applying coarse graining to the transaction result as needed.pub fn check_instantiate_result(result: &ContractInstantiateResult) -> Result<AccountId> { /* implementation */ } pub fn instantiate(code_hash: Hash, input_data: Vec<u8>, salt: Vec<u8>, mode: ExecutionMode, args: TransactionArguments) -> ContractInstantiateResult { /* implementation */ } pub fn bare_call(address: AccountId, input_data: Vec<u8>, mode: ExecutionMode, tx_args: TransactionArguments) -> ContractExecResult { /* implementation */ }
contract_tx
is a helper function that wraps the execution of a contract transaction, handling gas payment and refunding based on whether the transaction specifies free gas usage. It encapsulates the logic for deducting gas costs and refunding unused gas.fn contract_tx<T>(origin: AccountId, gas_limit: Weight, gas_free: bool, tx_fn: impl FnOnce() -> ContractResult<T>) -> ContractResult<T> { /* implementation */ }
This file is designed to facilitate interactions with smart contracts for the Pink pallet. It includes utilities for normalizing transaction costs through bit masking, handling the results of contract interactions, and executing contract instantiations and calls with consideration for gas management and execution modes.
This defines a pallet, reusable in the framework, providing functionalities for the execution, gas pricing, treasury management, and contract deployment tracking within a blockchain ecosystem. Below is a detailed explanation of its components:
Config
Trait)Config
Trait: Defines configuration parameters for the pallet, including dependencies on external components such as frame_system
and currency handling through the Currency
trait.pub trait Config: frame_system::Config { type Currency: Currency<Self::AccountId>; }
ClusterId
: Stores a unique identifier for the contract cluster.GasPrice
, DepositPerByte
, DepositPerItem
: Manage the economic parameters of contract execution.TreasuryAccount
: Holds the account ID of the treasury to which fees are paid.Key
: Stores the private key associated with the contract cluster.SidevmCodes
: A mapping from hash to WASM code for SideVMs, storing contract code.SystemContract
: Stores the account ID of the system contract.NextEventBlockNumber
and LastEventBlockHash
track event sequencing and integrity.Error
Enum)Error
Enum: Defines potential errors that can occur during the pallet's operation, such as unrecognized chain extension IDs or functions, buffer overflows, and cryptographic issues.pub enum Error<T> { ... }
Pallet
Struct)Pallet
Struct: Acts as the main entry point for the pallet's functionalities, with implementations extending the capabilities of the blockchain's contract layer.pub struct Pallet<T>(_);
AddressGenerator
trait to deterministically generate contract addresses based on inputs such as the deploying address, code hash, and a nonce (salt).impl<T: Config + pallet_contracts::Config> AddressGenerator<T> for Pallet<T> { ... }
set_cluster_id
, set_key
, put_sidevm_code
, and set_system_contract
enable configuration and management of contract execution parameters.BalanceOf
, AccountIdOf
, HashOf
) improve code readability and maintenance by abstracting complex type definitions.This pallet facilitates contract deployment, execution monitoring, and economic adjustments, ensuring a flexible and efficient contract ecosystem. Through its storage items, configuration traits, and utility functions, it provides a comprehensive suite of tools for blockchain developers to manage and extend their smart contract capabilities.
This Rust code defines a contract that extends the functionalities of pink contracts with additional capabilities, focusing on a chain extension for Pink Runtime. It involves complex interactions with blockchain state, events, cryptography, and contract-specific functionalities.
The contract is designed to provide extended functionalities to pink contracts within the Pink Runtime, a substrate-based blockchain environment. Key components include handling HTTP requests, cryptographic operations, cache management, event logging, and more, specifically tailored for contract execution contexts (query and command).
Defines a chain extension that enables pink contracts to access extended functionalities not directly available in the substrate pallet.
#[derive(Default)] pub struct PinkExtension;
Implements the ChainExtension
trait for Pink Runtime, providing a mechanism to call extended functions using environment variables, handling errors, and ensuring only known extension IDs and function IDs are processed.
impl ChainExtension<PinkRuntime> for PinkExtension { ... }
A structure that encapsulates an account ID representing a contract's address and implements the PinkRuntimeEnv
and PinkExtBackend
traits.
struct CallInQuery { address: AccountId, }
It provides functionalities such as HTTP request handling, cryptographic operations, and cache management, leveraging OCallImpl
for off-chain operations and DefaultPinkExtension
for some default implementations.
impl PinkRuntimeEnv for CallInQuery { ... } impl PinkExtBackend for CallInQuery { ... }
Similar to CallInQuery
, but used in command contexts. It ensures deterministic behavior, which is crucial for transactional operations.
struct CallInCommand { as_in_query: CallInQuery, }
Overrides methods to provide deterministic outputs or simulate the effects of operations that would be nondeterministic or unsupported in transactions, such as HTTP requests or JavaScript evaluations.
impl PinkExtBackend for CallInCommand { ... }
CallInQuery
implementation or providing default, deterministic responses.The contract extends pink contracts' capabilities within the Pink Runtime, providing a structured way to interact with blockchain functionalities while adhering to the determinism required in blockchain environments. Through CallInQuery
and CallInCommand
, it offers a flexible approach to handle different execution contexts, ensuring that contracts can perform a wide range of operations safely and deterministically.
This Rust module provides a framework for executing and committing transactions within the storage system, leveraging the external storage and Substrate's state machine (sp_state_machine
) functionalities.
Storage
encapsulates a generic backend and provides functionalities to execute code within a specified runtime context, manipulate overlay changes (temporary storage changes before they're committed), and commit these changes to the underlying storage backend.pub struct Storage<Backend> { backend: Backend, }
execute_with
function allows executing a closure with access to the blockchain state, capturing any side effects and overlay changes. This function sets the environment based on the execution context, including the current block number and timestamp, and handles the start and end of transactions.pub fn execute_with<R>(&self, exec_context: &ExecContext, f: impl FnOnce() -> R) -> (R, ExecSideEffects, OverlayedChanges<Hashing>)
execute_mut
is similar to execute_with
but additionally commits the changes captured in the overlay to the backend storage, making them persistent.pub fn execute_mut<R>(&mut self, context: &ExecContext, f: impl FnOnce() -> R) -> (R, ExecSideEffects)
commit_changes
directly commits overlay changes to the storage backend, transforming the ephemeral state changes into persistent storage modifications.pub fn commit_changes(&mut self, changes: OverlayedChanges<Hashing>)
CommitTransaction
trait is defined for storage backends capable of committing a series of changes as a transaction. Implementations of this trait must define how to commit these transactions to their specific storage medium.pub trait CommitTransaction: StorageBackend<Hashing> { fn commit_transaction(&mut self, root: Hash, transaction: BackendTransaction<Hashing>); }
maybe_emit_system_event_block
function allows for the emission of system event blocks, packaging events generated by contract executions into blocks that can be externally verified for integrity. This functionality supports privacy while enabling external accessibility and verification of contract events.pub fn maybe_emit_system_event_block(events: SystemEvents)
This module establishes the comprehensive system for executing the blockchain logic within a customizable storage context, efficiently handling storage transactions, and facilitating the emission of verifiable system events. It abstracts the complexities of interacting with the blockchain storage layer, providing a flexible and robust foundation for blockchain and smart contract development.
This module outlines an ExternalDB
struct, a custom implementation of the TrieBackendStorage
trait from the Substrate framework, designed to interface with an external key-value storage system. This setup allows the blockchain runtime to interact with storage not managed directly by the blockchain node, but rather, through "ocalls" (outward calls to the host environment or operating system).
ExternalDB
)ExternalDB
: Acts as a bridge for the blockchain trie storage backend to interact with an external storage system. It does not manage storage directly but forwards storage operations to an external handler via ocalls.pub struct ExternalDB;
ExternalBackend
& ExternalStorage
: These types are specialized versions of the TrieBackend
and Storage
constructs, respectively, using ExternalDB
for their storage operations. This allows the trie operations to be conducted with data that resides outside the blockchain node's direct control.pub type ExternalBackend = TrieBackend<ExternalDB, Hashing>; pub type ExternalStorage = Storage<ExternalBackend>;
TrieBackendStorage
for ExternalDB
overrides the get
method to fetch data from the external storage. This is done by making an ocall to OCallImpl.storage_get
, passing the requested key and receiving the value, if any, from the external storage system.impl TrieBackendStorage<Hashing> for ExternalDB { ... }
CommitTransaction
for ExternalBackend
enables the backend to commit a set of changes to the external storage. Changes are sent via OCallImpl.storage_commit
, encapsulating the new root hash and the list of key-value changes to be applied.impl CommitTransaction for ExternalBackend { ... }
instantiate
function for ExternalStorage
constructs a new instance of external storage. It starts by fetching the current storage root via an ocall (OCallImpl.storage_root()
), then builds a trie backend around ExternalDB
with this root, and finally wraps this backend in a Storage
instance.impl ExternalStorage { ... }
code_exists
checks for the presence of contract code in the external storage. It generates a storage key specific to the code hash and queries the external storage to verify its existence.pub mod helper { ... }
This module showcases how storage operations can be extended to utilize external storage systems. By delegating storage operations through ocalls
, it facilitates interaction with storage mechanisms not inherently managed by the blockchain node, enabling more flexible and decentralized storage solutions. This setup is particularly useful in scenarios where off-chain data needs to be securely accessed and manipulated by on-chain logic, bridging the gap between the blockchain and external systems.
This defines the entry point and function dispatching for a runtime environment specifically designed for pink contracts. It involves interacting with a C interface, managing initialization parameters, and routing external calls (ecalls) to their respective implementations.
Imports necessary modules and types from pink_capi::v1
and other crates. logger
is used for logging purposes, and OCallImpl
is a module that presumably handles outgoing calls from the contract.
use pink_capi::v1; use v1::*; use phala_sanitized_logger as logger; pub(crate) use ocall_impl::OCallImpl;
Defines a constant to ensure the __pink_runtime_init
function matches the init_t
type. This function initializes the runtime environment, setting up necessary configurations and ecall functions.
const _: init_t = Some(__pink_runtime_init);
__pink_runtime_init()
Marks the beginning of the runtime execution. It initializes the runtime with provided configuration and populates the ecall table with supported ecall functions.
unsafe
due to direct pointer manipulations.config
: A pointer to the runtime configuration.ecalls
: A mutable pointer to the ecalls table that will be populated by this function.#[no_mangle] pub unsafe extern "C" fn __pink_runtime_init(config: *const config_t, ecalls: *mut ecalls_t) -> ::core::ffi::c_int { ... }
Defines a function to report the runtime's version. This is likely used by external callers to determine the capabilities or version of the runtime they are interacting with.
unsafe extern "C" fn get_version(major: *mut u32, minor: *mut u32) { ... }
Serves as a central dispatcher for all ecall functions. It decodes input data and routes the call to the appropriate function within the runtime based on the call_id
.
unsafe
due to raw pointer interactions.call_id
: Identifies the function to be called.data
, len
: Pointers to the input data buffer and its length.ctx
: A context passed to the output_fn
.output_fn
: A function pointer to handle the output from the ecall.unsafe extern "C" fn ecall(call_id: u32, data: *const u8, len: usize, ctx: *mut ::core::ffi::c_void, output_fn: output_fn_t) { ... }
The contract is essentially a bridge between the pink contracts runtime and external (possibly non-Rust) environments, facilitating initialization, configuration, and function dispatching. It demonstrates how Rust can interact with C-style interfaces, using unsafe code for pointer manipulation and FFI interactions. The contract provides a structured way to initialize the runtime, handle versioning, and route external calls to their respective implementations, showcasing a practical example of extending Rust-based blockchain runtime with external functionalities.
Comprehensive implementation of the ECall (Enclave Call) interface for a blockchain or enclave runtime, facilitating the execution of smart contracts and interaction with the blockchain state. It integrates closely with the external environment, managing contract instantiation, execution, and communication between the contract and the blockchain.
ECallImpl
: Serves as the primary implementation for handling ECall operations. It doesn't contain state by itself but defines methods to interact with the blockchain state and external calls.pub struct ECallImpl;
storage
Function: Instantiates an external storage interface, allowing contracts to interact with off-chain storage.pub(crate) fn storage() -> crate::storage::ExternalStorage { ... }
instrument_context
Macro: Instruments the execution context with additional tracing and logging for diagnostics. It logs the block number and request ID, aiding in debugging and monitoring.macro_rules! instrument_context { ... }
Executing
for ExternalStorage
: Allows executing functions within a managed execution context, automatically handling logging, side effects, and state changes.impl Executing for crate::storage::ExternalStorage { ... }
ECalls
Implementation: Defines a series of operations related to smart contracts, including their setup, instantiation, and execution. Key functionalities include:
cluster_id
: Retrieves the unique identifier of the contract cluster.setup
: Initializes the contract cluster with specific configuration parameters.deposit
: Handles the deposit of funds to a specific account.set_key
and get_key
: Manages the cryptographic key associated with the contract.upload_code
: Uploads the smart contract code to the blockchain.upload_sidevm_code
and get_sidevm_code
: Manages SideVM (possibly a virtual machine or execution environment) code uploading and retrieval.system_contract
: Retrieves the system contract's account identifier.free_balance
and total_balance
: Queries the balance information of a specific account.code_hash
: Fetches the hash of the contract code associated with an account.contract_instantiate
and contract_call
: Handles the instantiation and execution of contracts.git_revision
: Returns the git revision of the codebase, aiding in version tracking.on_genesis
, on_runtime_upgrade
, and on_idle
for contract lifecycle management.impl ecall::ECalls for ECallImpl { ... }
sanitize_args
: Adjusts the transaction arguments based on the execution mode to ensure reasonable gas limits and prevent abuse.handle_deposit
: Facilitates deposit transactions as part of contract call preparations.This module is a crucial part of the contract execution framework, bridging the gap between the sandboxed execution environment and the blockchain. It ensures that smart contracts can be instantiated, executed, and interact with the blockchain securely and efficiently. Through the implementation of the ECalls
interface, it provides a rich set of functionalities for contract management, execution, and interaction with the blockchain state, all while ensuring security and determinism in contract execution.
This module provides a comprehensive setup for handling outwards calls (Ocalls) from a sandboxed environment, such as a blockchain smart contract or a secure enclave, to the outside world. It's part of a broader framework likely aimed at enhancing blockchain or enclave applications with external functionalities through a cross-call interface.
The core of this module revolves around the ability to make calls from a sandboxed environment to the host environment's functions. This is crucial for extending the functionalities of smart contracts or secure enclaves by leveraging external services or resources. The setup involves:
cross_call
and cross_call_mut
methods) to execute these calls, with safety ensured through raw pointers and external function pointers.In environments with custom memory management needs, such as secure enclaves or smart contracts, it's essential to have a way to manage memory allocations that align with the host's expectations. The conditional inclusion of a custom allocator allows this module to seamlessly integrate with the host environment's memory management schemes, ensuring efficient and accurate memory usage tracking.
The module includes tests to ensure that setting the OCall function works as expected and that the default OCall behavior is correctly panic-inducing. These tests, alongside the careful use of unsafe
blocks, underscore the importance of safety and correctness in cross-boundary function calls.
OCALL
Static Variable: Holds the actual OCall function that's provided by the host environment. Initialized with a default function (_default_ocall
) that panics, indicating no function has been provided yet.static mut OCALL: InnerType<cross_call_fn_t> = _default_ocall;
_default_ocall
Function: A placeholder OCall function that panics. This is used until a real OCall function is set via set_ocall_fn
.unsafe extern "C" fn _default_ocall(...) { panic!("No ocall function provided"); }
set_ocall_fn
Function: Allows setting the actual OCall function provided by the external environment. This function also sets up custom allocator and deallocator functions if available and the allocator
feature is enabled.pub(super) fn set_ocall_fn(ocalls: ocalls_t) -> Result<(), &'static str> { ... }
OCallImpl
Struct: Represents the implementation of the cross-call interface for outgoing calls.pub(crate) struct OCallImpl;
OCallImpl
implements both CrossCall
and CrossCallMut
traits, providing mechanisms for making synchronous calls to external functions.impl CrossCall for OCallImpl { ... } impl CrossCallMut for OCallImpl { ... }
The module conditionally includes a custom memory allocator setup, redirecting allocation and deallocation calls to the external environment, ensuring memory usage statistics remain accurate across dynamic runtime boundaries.
#[cfg(feature = "allocator")] mod allocator { ... }
This module lays the foundation for a flexible and secure mechanism to extend the capabilities of sandboxed environments like blockchain smart contracts or secure enclaves. It carefully manages the intricacies of cross-call setup, execution, and memory management, providing a critical bridge between the sandboxed and host environments.
This works on scenarios where contracts need to communicate with external systems or require data from outside their isolated execution environment.
Imports custom data types from a separate module, preparing them for use in defining interfaces and data structures throughout the current context.
Define the foundational interface for making cross-boundary calls, allowing contracts to request operations outside their execution scope. The CrossCall
trait facilitates read-only operations, while CrossCallMut
supports mutations.
Specifies methods for executing provided closures either immutably or mutably, enabling flexible execution patterns based on the current execution context.
An implementation of the Executing
trait that directly executes provided functions without modification, serving as a straightforward execution path.
Marker traits that don't introduce any methods but signify different types of cross-call interactions. ECall
is used for inward calls into the contract environment, while OCall
represents outward calls from the contract.
Focused on inward-bound calls (Ecalls), allowing contracts to perform various operations like managing cluster setup, handling balances, and interacting with contract code. Each operation is uniquely identified and designed to facilitate essential contract lifecycle and interaction tasks.
Define data structures for specifying the arguments to transactions and the configuration for cluster setup, respectively. These structs include fields for managing balances, gas limits, and contract deployment specifics.
Defines a comprehensive set of operations available to contracts, including cluster management, balance adjustments, contract code handling, and more. Each method is associated with a unique cross-call ID, enabling precise and controlled execution of contract requests.
Targets outward-bound calls (Ocalls), defining the means through which contracts can interact with external systems and perform operations like storage management, logging, caching, and HTTP requests.
Provides detailed context about the current execution environment, including mode, block number, and timestamp, enabling contracts to make informed decisions based on their operational context.
Outlines a broad set of functionalities for contracts to interact with their environment, including storage operations, logging, cache management, HTTP requests, and handling system events. This trait ensures contracts have access to necessary external data and services, supporting complex and dynamic contract behaviors.
Includes test setups and coverage for ctypes, demonstrating instantiation and cloning of test structures to ensure the reliability and functionality of defined types and operations.
The provided Rust code constructs a comprehensive framework for smart contract execution, enabling complex interactions within a blockchain environment. By defining clear interfaces for cross-boundary calls, execution patterns, and external operations, it facilitates a wide range of contract behaviors, from simple value transfers to complex interactions with external data sources and systems.
This contract uses several basic blockchain types, an execution mode enumeration to distinguish between different runtime contexts, and a structure to handle events and their side effects resulting from contract execution. Here's a closer look at the key functionalities:
The contract establishes fundamental blockchain types such as Hash
, AccountId
, Balance
, and BlockNumber
, employing BlakeTwo256
as the default hashing algorithm. These types are essential for creating, identifying, and managing accounts, transactions, and other blockchain entities.
ExecutionMode
is an enum that identifies the current context in which the runtime is executing: Query
, Estimating
, or Transaction
. Each mode is designed for specific operations:
Utility methods associated with ExecutionMode
facilitate checks on the current mode and determine operational behaviors like gas estimation and the requirement for deterministic execution.
ExecSideEffects
captures the potential side effects of contract execution. In its current version (V1
), it contains vectors for events emitted by Pink and ink! contracts and a list of instantiated contracts. A noteworthy feature is its ability to filter events permissible in query contexts, allowing for a controlled execution environment that respects the constraints of different runtime modes.
This contract structure provides a robust framework for managing the execution context and handling the side effects of contract operations. By distinguishing between querying, estimating, and transaction modes, it ensures that contracts can operate effectively in various scenarios, from gas estimation to actual transaction execution.
The explicit handling of execution side effects and events also adds a layer of transparency and control, allowing contract developers and runtime environments to manage how contract actions influence the broader system and interact with other contracts.
In summary, the contract's design promotes secure, efficient, and flexible blockchain operations within the Pink Protocol environment, catering to both the deterministic requirements of blockchain transactions and the need for flexibility in queries and gas estimation.
This contract showcases the integration of HTTP
requests, cryptographic operations, caching, and logging within a blockchain runtime environment. Below is a summary of key functionalities, broken down for clarity:
PinkRuntimeEnv
: Defines a trait for accessing the address of the current environment, ensuring compatibility with various blockchain account formats.DefaultPinkExtension
: A struct that encapsulates the environment and provides default implementations for the PinkExtBackend trait, facilitating interactions with external HTTP services, cryptographic operations, and more.http_request
and batch_http_request
: Functions to perform synchronous and asynchronous HTTP requests, respectively, with support for handling request errors, setting request timeouts, and processing batch requests efficiently.async_http_request
: An asynchronous function to execute an HTTP request, handling URL parsing, client creation, method determination, header processing, and response handling, including error scenarios.sr25519
, ed25519
, ecdsa
), allowing for the generation and verification of cryptographic signatures using various key types.cache_set
, cache_get
, cache_remove
, cache_set_expiration
) allow storing and retrieving data efficiently, with the capability to set expiration times for cached entries.LimitedWriter
: A custom writer that imposes a size limit on the amount of data that can be written, preventing excessive resource usage.The code includes comprehensive unit tests to verify the functionality of HTTP request processing, cryptographic operations, caching, logging, and other features provided by the contract. These tests ensure the reliability and security of the contract's operations within a blockchain environment.
This contract demonstrates a sophisticated integration of network communication, cryptographic security, data management, and debugging facilities within a blockchain runtime, aiming to provide a robust foundation for developing decentralized applications on the Pink network.
The LocalCache module is designed to provide off-chain, local key-value (KV) storage for smart contracts. This cache allows for data storage that is unique to each instance of the contract running on different nodes or machines, with the understanding that stored data may be lost upon restarts or due to expiration mechanisms. Here's a closer look at key functionalities within this module:
LocalCache
: Manages multiple Storage
instances, each identified by a contract ID, providing a mechanism to store, retrieve, and manage cached data with considerations for garbage collection (GC), expiration, and quota limits.Storage
: Represents the storage for a single contract, containing key-value pairs (kvs
), tracking the total size of stored data, and enforcing a maximum storage size to adhere to quotas.set
, get
, remove
, set_expiration
: Core operations to manipulate the cache, including setting and getting values, removing entries, and setting custom expiration times for entries.apply_cache_op
: Applies higher-level cache operations (CacheOp
) like setting values, setting expiration times, and removing entries, abstracted for ease of use within smart contracts.clear_expired
: Removes expired entries based on the current time to free up space and ensure data freshness.fit_size
: Ensures the storage does not exceed its quota by removing the least recently used or soonest to expire entries until the total size is within limits.maybe_clear_expired
: Periodically triggers clear_expired
based on a predefined interval (gc_interval
) to maintain cache hygiene without overly frequent checks.enable_test_mode
, apply_quotas
: Special functions for testing and development, allowing the simulation of different cache environments and managing storage quotas for each contract.The LocalCache module offers a robust, flexible caching solution for off-chain computations in smart contracts. It intelligently manages data with expiration, garbage collection, and storage quotas, ensuring efficient use of resources. The module's design caters to both operational needs and development/testing environments, showcasing a well-thought-out approach to local caching within blockchain applications.
mask_low_bits_works
) shows the limited unit testing coverage. Comprehensive testing, including edge cases for gas masking and contract execution scenarios, is essential for ensuring the reliability and security of these utilities.default_value_lifetime
). This might not be flexible enough for all use cases. Allowing custom lifetimes per item could provide more granular control over cache expiration.local_cache.with_global_cache()
function uses a Mutex in non-test mode and RefCell in test mode. While Mutex provides thread safety, RefCell allows borrowing for mutation, but only one mutable borrow can exist at a time. This can lead to issues if the cache is accessed from multiple threads concurrently in non-test mode leading to an inequality in execution results from test and non-test modes.My attempt on reviewing the Codebase spanned around 60 hours distributed over 9 days:
The codebase was a very great learning experience, though it was a pretty hard nut to crack being purely based on the Rust language, nonetheless during my review, I uncovered a few issues within the protocol and they need to be fixed. Recommended steps should be taken to protect the protocol from potential attacks. Timely audits and codebase cleanup for mitigations should also be conducted to keep the codebase fresh and up to date with evolving security times.
Documentation: https://docs.phala.network/developers/phat-contract
Website: https://phala.network/
Audit Page: https://code4rena.com/audits/2024-03-phat-contract-runtime#top
Documentation for the Phala Blockchain: https://github.com/Phala-Network/phala-blockchain/tree/master/docs
060 hours
#0 - c4-pre-sort
2024-03-25T07:13:55Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2024-03-27T16:01:07Z
OpenCoreCH marked the issue as grade-b