Skip to content

solidity-utilities/library-mapping-string

Repository files navigation

Library Mapping String

Solidity library for mapping of string key/value pairs

Byte size of Library Mapping String Open Issues Open Pull Requests Latest commits Build Status



Requirements

Prerequisites and/or dependencies that this project needs to function properly

This project utilizes Truffle for organization of source code and tests, thus it is recommended to install Truffle globally to your current user account

npm install -g truffle

Quick Start

Perhaps as easy as one, 2.0,...

NPM and Truffle are recommended for importing and managing project dependencies

cd your_project

npm install @solidity-utilities/library-mapping-string

Note, source code for this library will be located within the node_modules/@solidity-utilities/library-mapping-string directory of your_project root

Solidity contracts may then import code via similar syntax as shown

import {
    LibraryMappingString
} from "@solidity-utilities/library-mapping-string/contracts/LibraryMappingString.sol";

Note, above path is not relative (ie. there's no ./ preceding the file path) which causes Truffle to search the node_modules sub-directories

Review the Truffle -- Package Management via NPM documentation for more installation details.


In the future, after beta testers have reported bugs and feature requests, it should be possible to link the deployed LibraryMappingString via Truffle migration similar to the following.

migrations/2_your_contract.js

const LibraryMappingString = artifacts.require("LibraryMappingString");
const YourContract = artifacts.require("YourContract");

module.exports = (deployer, _network, _accounts) {
  LibraryMappingString.address = "...";
  deployer.deploy(LibraryMappingString, { overwrite: false });
  deployer.link(LibraryMappingString, YourContract);
  deployer.deploy(YourContract);
};

Usage

How to utilize this repository

Write contract(s) that make use of, and extend, LibraryMappingString features.

// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.7;

import {
    LibraryMappingString
} from "@solidity-utilities/library-mapping-string/contracts/LibraryMappingString.sol";

/// @title Programming interface for storing string mapping data
/// @author S0AndS0
contract StringStorage {
    using LibraryMappingString for mapping(string => string);
    mapping(string => string) data;
    mapping(string => uint256) indexes;
    string[] public keys;
    address public owner;

    constructor(address _owner) {
        owner = _owner;
    }

    /// @notice Requires message sender to be an instance owner
    /// @param _caller {string} Function name that implements this modifier
    modifier onlyOwner(string memory _caller) {
        string memory _message = string(
            abi.encodePacked(
                "StringStorage.",
                _caller,
                ": message sender not an owner"
            )
        );
        require(msg.sender == owner, _message);
        _;
    }

    /// @notice Retrieves stored value `string` or throws an error if _undefined_
    /// @dev Passes `_key` to `data.getOrError` with default Error `_reason` to throw
    /// @param _key **{string}** Key mapped to value
    /// @return **{string}** Value for corresponding `_key`
    /// @custom:throws **{Error}** `"StringStorage.get: value not defined"`
    function get(string calldata _key) external view returns (string memory) {
        return data.getOrError(_key, "StringStorage.get: value not defined");
    }

    /// @notice Check if `string` key has a corresponding value `string` defined
    /// @dev Forwards parameter to `data.has`
    /// @return **{bool}**
    function has(string calldata _key) external view returns (bool) {
        return data.has(_key);
    }

    /// @notice Allow full read access to all `keys` stored within `data`
    /// @dev **Warning** Key order is not guarantied
    /// @return **{string[]}**
    function listKeys() external view returns (string[] memory) {
        return keys;
    }

    /// @notice Delete value `string` for given `_key`
    /// @dev **Warning** Overwrites current key with last key
    /// @dev Passes parameters to `data.removeOrError` with default Error `_reason` to throw
    /// @return **{string}**
    /// @custom:javascript Returns transaction object
    /// @custom:throws **{Error}** `"StringStorage.remove: message sender not an owner"`
    /// @custom:throws **{Error}** `"StringStorage.remove: value not defined"`
    /// :bookmark: Track test fixes and dependency upgrades
    function remove(string calldata _key)
        external
        onlyOwner("remove")
        returns (string memory)
    {
        string memory _value = data.removeOrError(
            _key,
            "StringStorage.remove: value not defined"
        );
        uint256 _last_index = keys.length - 1;
        string memory _last_key = keys[_last_index];
        if (keys.length > 1) {
            uint256 _target_index = indexes[_key];
            keys[_target_index] = keys[_last_index];
            indexes[_last_key] = _target_index;
        }
        delete indexes[_last_key];
        keys.pop();
        return _value;
    }

    /// @notice Call `selfdestruct` with provided address
    /// @param _to **{address}** Where to transfer any funds this contract has
    /// @custom:throws **{Error}** `"StringStorage.selfDestruct: message sender not an owner"`
    function selfDestruct(address payable _to)
        external
        onlyOwner("selfDestruct")
    {
        selfdestruct(_to);
    }

    /// @notice Store `_value` under given `_key` while preventing unintentional overwrites
    /// @param _key **{string}** Mapping key to set corresponding value `string` for
    /// @param _value **{string}** Mapping value to set
    /// @custom:throws **{Error}** `"StringStorage.set: message sender not an owner"`
    /// @custom:throws **{Error}** `"StringStorage.set: value already defined"`
    function set(string calldata _key, string calldata _value)
        external
        onlyOwner("set")
    {
        data.setOrError(
            _key,
            _value,
            "StringStorage.set: value already defined"
        );
        keys.push(_key);
    }

    /// @notice Number of key/value `address` pairs stored
    /// @dev Cannot depend on results being valid if mutation is allowed between calls
    /// @return **{uint256}** Length of `keys` array
    /// @custom:javascript Returns `BN` data object
    function size() external view returns (uint256) {
        return keys.length;
    }
}

Above the StringStorage contract;

  • maintains a list of keys

  • restricts mutation to owner only

There is likely much that can be accomplished by leveraging these abstractions, check the API section for full set of features available. And review the test/test__examples__StringStorage.js file for inspiration on how to use this library within projects.


API

Application Programming Interfaces for Solidity smart contracts

Developer note -> Check the test/ directory for JavaScript and Solidity usage examples


Library LibraryMappingString

Organizes methods that may be attached to mapping(string => string) type

Warning any value of "" is treated as null or undefined

Source contracts/LibraryMappingString.sol


Method get

Retrieves stored value string or throws an error if undefined

Source get(mapping(string => string) _self, string _key)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key string to lookup corresponding value string for

Returns -> {string} Value for given key string

Throws -> {Error} "LibraryMappingString.get: value not defined"

Developer note -> Passes parameters to getOrError with default Error _reason to throw


Method getOrElse

Retrieves stored value string or provided default string if undefined

Source getOrElse(mapping(string => string) _self, string _key, string _default)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key string to lookup corresponding value string for

  • _default {string} Value to return if key string lookup is undefined

Returns -> {string} Value string for given key string or _default if undefined


Method getOrError

Allows for defining custom error reason if value string is undefined

Source getOrError(mapping(string => string) _self, string _key, string _reason)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key string to lookup corresponding value string for

  • _reason {string} Custom error message to throw if value string is undefined

Returns -> {string} Value for given key string

Throws -> {Error} _reason if value is undefined


Method has

Check if string key has a corresponding value string defined

Source has(mapping(string => string) _self, string _key)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key to check if value string is defined

Returns -> {bool} true if value string is defined, or false if undefined


Method overwrite

Store _value under given _key without preventing unintentional overwrites

Source overwrite(mapping(string => string) _self, string _key, string _value)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key to set corresponding value string for

  • _value {string} Mapping value to set

Throws -> {Error} "LibraryMappingString.overwrite: value cannot be """

Developer note -> Passes parameters to overwriteOrError with default Error _reason to throw


Method overwriteOrError

Store _value under given _key without preventing unintentional overwrites

Source overwriteOrError(mapping(string => string) _self, string _key, string _value, string _reason)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key to set corresponding value string for

  • _value {string} Mapping value to set

  • _reason {string} Custom error message to present if value string is ""

Throws -> {Error} _reason if value is ""


Method remove

Delete value string for given _key

Source remove(mapping(string => string) _self, string _key)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key to delete corresponding value string for

Returns -> {string} Value for given key string

Throws -> {Error} "LibraryMappingString.remove: value not defined"

Developer note -> Passes parameters to removeOrError with default Error _reason to throw


Method removeOrError

Delete value string for given _key

Source removeOrError(mapping(string => string) _self, string _key, string _reason)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key to delete corresponding value string for

  • _reason {string} Custom error message to throw if value string is undefined

Returns -> {string} Stored value string for given key string

Throws -> {Error} _reason if value is undefined


Method set

Store _value under given _key while preventing unintentional overwrites

Source set(mapping(string => string) _self, string _key, string _value)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key to set corresponding value string for

  • _value {string} Mapping value to set

Throws

  • {Error} "LibraryMappingString.set: value already defined"

  • {Error} "LibraryMappingString.setOrError: value cannot be """

Developer note -> Passes parameters to setOrError with default Error _reason to throw


Method setOrError

Stores _value under given _key while preventing unintentional overwrites

Source setOrError(mapping(string => string) _self, string _key, string _value, string _reason)

Parameters

  • _self {mapping(string => string)} Mapping of key/value string pairs

  • _key {string} Mapping key to set corresponding value string for

  • _value {string} Mapping value to set

  • _reason {string} Custom error message to present if value string is defined

Throws

  • {Error} _reason if value is defined

  • {Error} "LibraryMappingString.setOrError: value cannot be """


Notes

Additional things to keep in mind when developing

Implementing selfdestruct on contracts that utilize this library is recommended. Because as explained by Ethereum Storage, it is not a good idea to store much data, especially dynamically sized types such as strings, on the Ethereum blockchain.

Instead it be better to store large amounts of data, such as files, via services such as;

... and then use this library for tracking references to such assets.

Additionally it is a very bad idea to store any data that may be considered private, even if encrypted, within contracts.


This repository may not be feature complete and/or fully functional, Pull Requests that add features or fix bugs are certainly welcomed.


Contributing

Options for contributing to library-mapping-string and solidity-utilities


Forking

Start making a Fork of this repository to an account that you have write permissions for.

cd ~/git/hub/solidity-utilities/library-mapping-string

git remote add fork [email protected]:<NAME>/library-mapping-string.git
  • Commit your changes and push to your fork, eg. to fix an issue...
cd ~/git/hub/solidity-utilities/library-mapping-string


git commit -F- <<'EOF'
:bug: Fixes #42 Issue


**Edits**


- `<SCRIPT-NAME>` script, fixes some bug reported in issue
EOF


git push fork main

Note, the -u option may be used to set fork as the default remote, eg. git push -u fork main however, this will also default the fork remote for pulling from too! Meaning that pulling updates from origin must be done explicitly, eg. git pull origin main

  • Then on GitHub submit a Pull Request through the Web-UI, the URL syntax is https://github.com/<NAME>/<REPO>/pull/new/<BRANCH>

Note; to decrease the chances of your Pull Request needing modifications before being accepted, please check the dot-github repository for detailed contributing guidelines.


Sponsor

Thanks for even considering it!

Via Liberapay you may sponsor__shields_io__liberapay on a repeating basis.

Regardless of if you're able to financially support projects such as library-mapping-string that solidity-utilities maintains, please consider sharing projects that are useful with others, because one of the goals of maintaining Open Source repositories is to provide value to the community.


Attribution


License

Solidity library for mapping of string key/value pairs
Copyright (C) 2021 S0AndS0

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

For further details review full length version of AGPL-3.0 License.