How to save strings in the storage of dapp?

Hello everyone!
In an attempt to implement a token contract on fuel I forked swayswap’s token contract and noticed that swayswap’s token doesn’t support the same fields like name, symbol, and decimals.

Then in one of my previous topics @david sent me a topic with src-20 fungible token standard, but there is no name, symbol implementation too…
And today I decided to figure out how I can store strings in the storage.

In the progress of my research I found some solutions, let’s take a closer look at them:

  1. First of all I decided to use str[n] like a string. But there is one big minus: we definitely need to know the length of the line.
    In that case, we can hardcode some strings before deploying the dApp, but it’s impossible to change the strings.
contract;

abi MyContract {
     #[storage(read)]
    fn name() -> str[9];
}

storage {
    name: str[9] = "sway gang",
}

impl MyContract for Contract {
     #[storage(read)]
    fn name() -> str[9] {
        storage.name
    }
}
  1. The next thing that came to my mind is store strings like str[32]. Here we can store and change strings less than 32 symbols like sway gang_______________________ and each time you write/read remove/add to the end of the line symbols _ in quantity 32 - string_length. But it looks ugly and not convenient.
contract;

abi MyContract {
     #[storage(read)]
    fn name() -> str[32];
     #[storage(write)]
    fn set_name(name: str[32]);
}

storage {
    name: str[32] = "sway gang_______________________",
}

impl MyContract for Contract {
    #[storage(read)]
    fn name() -> str[32] {
        storage.name
    }

    #[storage(write)]
    fn set_name(name: str[32]){
        storage.name = name;
    }
}

  1. After that I discovered there is awesome package sway_libs::string::String. And we can use it very well but… Yup the strings library is excellent for dynamically-sized strings. That type does not work in a storage block though because it’s a heap type.
    Btw I will show what the code can look like. Anyway you can copy the code and use Strings without storage

Forc.toml

[project]
authors = ["SWAY GANG"]
entry = "main.sw"
license = "Apache-2.0"
name = "name_contract"

[dependencies]
sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.4.1" }

main.sw

contract;
use sway_libs::string::String;

abi MyContract {
     #[storage(read)]
    fn name() -> String;
     #[storage(write)]
    fn set_name(name: String);
}

storage {
    name: String = String::new(), // here code is wrong
}

impl MyContract for Contract {
    #[storage(read)]
    fn name() -> String {
        storage.name
    }

    #[storage(write)]
    fn set_name(name: String){
        storage.name = name;
    }
}

  1. So, as under the hood package sway_libs::string::String use Vec<u8> my final try was with using StorageVec<u8>. The contract will look like that
contract;
use std::{storage::StorageVec};
abi MyContract {
    #[storage(read)]
    fn name() -> StorageVec<u8>;
    // todo
    // #[storage(write)]
    // fn set_name(name: StorageVec<u8>);
}

storage {
    name:  StorageVec<u64> = StorageVec {}, 
}

impl MyContract for Contract {
    #[storage(read)]
    fn name() -> StorageVec<u8> {
        storage.name
    }

    // todo
    // #[storage(write)]
    // fn set_name(name: StorageVec<u8>){
    //     storage.name = name;
    // }
}

and the test will look like tahat:

use fuels::{prelude::*, tx::ContractId};

abigen!(MyContract, "out/debug/name_contract-abi.json");

async fn get_contract_instance() -> (MyContract, ContractId) {
    let mut wallets = launch_custom_provider_and_get_wallets(
        WalletsConfig::new(
            Some(1),           
            Some(1),             
            Some(1_000_000_000), 
        ),
        None,
        None,
    )
    .await;
    let wallet = wallets.pop().unwrap();

    let id = Contract::deploy(
        "./out/debug/name_contract.bin",
        &wallet,
        TxParameters::default(),
        StorageConfiguration::with_storage_path(Some(
            "./out/debug/name_contract-storage_slots.json".to_string(),
        )),
    )
    .await
    .unwrap();

    let instance = MyContract::new(id.clone(), wallet);

    (instance, id.into())
}

#[tokio::test]
async fn can_get_contract_id() {
    let (_instance, _id) = get_contract_instance().await;
    // todo
    // _instance.methods().set_name("SWAY GANG".as_bytes()).call().await.unwrap()
}

But after
forc build and cargo test it It’s falling with this error

error[E0392]: parameter `V` is never used
 --> tests/harness.rs:3:1
  |
3 | abigen!(MyContract, "out/debug/name_contract-abi.json");
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused parameter
  |
  = help: consider removing `V`, referring to it in a field, or using a marker such as `PhantomData`
  = note: this error originates in the macro `abigen` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0392`.
error: could not compile `name-contract` due to previous error

So, can someone explain to me what I’m doing wrong and how I store and update the name in the dApp store?

1 Like

Yeah unfortunately, str[n] is your only reasonable option at this point. This may change in the future as we make strings a bit more ergonomic.

Dynamic strings (i.e. String) and StorageVec` have various limitations that you ran into already.

I resolved this like that

...

pub struct Config {
    name: str[32],
    symbol: str[8],
    decimals: u8,
}


storage {
    config: Config = Config {
        name: "                                ",
        symbol: "        ",
        decimals: 1u8,
    },
    owner: Address = Address {
        value: ZERO_B256,
    },
    mint_amount: u64 = 0,
    mint_list: StorageMap<Address, bool> = StorageMap {},
}

...

#[storage(read)]
    fn config() -> Config {
        storage.config
    }

...
1 Like

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.