Error storing Transaction struct in storage

I am developing a smart contract that needs to store Transaction structs in storage until they can be executed. The Transaction struct is defined as follows:

/// The transaction that is being proposed.
pub struct Transaction {
    tx_id: TxId,
    to: Identity,
    tx_parameters: TransactionParameters,
}

pub type TxId = u64;

/// Determines the type of transaction parameters.
pub enum TransactionParameters {
    Call: ContractCallParams,
    Transfer: TransferParams,
}

/// Parameters for calling a contract.
pub struct ContractCallParams {
    /// The calldata for the call.
    calldata: Bytes,
    /// The amount of gas to forward.
    forwarded_gas: u64,
    /// The function selector for the call.
    function_selector: Bytes,
    /// Whether the function being called takes a single value-type argument.
    single_value_type_arg: bool,
    /// Parameters for a transfer.
    transfer_params: TransferParams,
}

/// Parameters for a transfer.
pub struct TransferParams {
    /// The asset to transfer.
    asset_id: ContractId,
    /// The amount to transfer.
    value: Option<u64>,
}

In my storage definition, I am getting the following error:

storage {
    ... 
    // Other fields
    
    /// The transactions that are currently active.
    txs: StorageMap<TxId, Transaction> = StorageMap {}, 
     -> Error: `The type "pointer" is not allowed in storage.`
    ...
}

This is the output of fuelup show:

Default host: aarch64-apple-darwin

installed toolchains
--------------------
beta-3-aarch64-apple-darwin
beta-4-aarch64-apple-darwin
latest-aarch64-apple-darwin (default)

active toolchain
----------------
latest-aarch64-apple-darwin (default)
  forc : 0.49.1
    - forc-client
      - forc-deploy : 0.49.1
      - forc-run : 0.49.1
    - forc-crypto : 0.49.1
    - forc-doc : 0.49.1
    - forc-explore : 0.28.1
    - forc-fmt : 0.49.1
    - forc-lsp : 0.49.1
    - forc-tx : 0.49.1
    - forc-wallet : 0.4.2
  fuel-core : 0.22.0
  fuel-core-keygen : 0.22.0

fuels versions
--------------
forc : 0.54.0
forc-wallet : 0.54.0
  • What is the recommended way to store a custom struct like Transaction in storage? Any guidance would be appreciated!

Hi there!
Thanks for raising this issue, the devEx team is already taking a look.
:palm_tree:

Transaction contains TransactionParams which contains ContractCallParams which contains Bytes which contains RawBytes which contains a raw_ptr, and that’s not allowed. Storage can’t contain heap types.

You need to use StorageBytes instead.

Using StorageBytes worked for making the compiler happy. But now when im actually retrieving the data, i cannot access it.

Recalling the fixed data types:

/// Determines the type of transaction parameters.
pub enum TransactionParameters {
    Call: ContractCallParams,
    Transfer: TransferParams,
}

/// Parameters for calling a contract.
pub struct ContractCallParams {
    /// The calldata for the call.
    calldata: StorageBytes,
    /// The amount of gas to forward.
    forwarded_gas: u64,
    /// The function selector for the call.
    function_selector: StorageBytes,
    /// Whether the function being called takes a single value-type argument.
    single_value_type_arg: bool,
    /// Parameters for a transfer.
    transfer_params: TransferParams,
}

When trying to access the calldata and function selector fields from the Transaction struct:

The approach shown below does not work because the type of contract_call_params.function_selector is StorageBytes, and it doesn’t have any methods to retrieve the content. I did some research and the only way it works is if the StorageBytes type is defined inside the storage by itself, because when accessing storage.function_selector its wrapped in a StorageKey.

let transaction:Transaction = storage.txs.get(tx_id).try_read().unwrap();
match transaction.tx_parameters {
    TransactionParameters::Call(contract_call_params) => {
            // I would expect something like this to work to retrieve the underlying Bytes
            let function_selector = contract_call_params.function_selector.read_slice().unwrap(); 
    }

How should i access the underlying bytes that were previously stored?

There are some limitations with the way the current storage interface works, but you should be able to store dynamic data as a key into another mapping.

Instead of a struct with StorageBytes you’d just have a u64 that’s a key into another storage slot’s StorageMap<u64, StorageBytes> or something to that effect.

Thanks! Will try that