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.

3 Likes

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.

2 Likes

Thanks! Will try that