Solidity library for mapping of string key/value pairs
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
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 ofyour_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 thenode_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); };
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.
Application Programming Interfaces for Solidity smart contracts
Developer note -> Check the test/
directory for
JavaScript and Solidity usage examples
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
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/valuestring
pairs -
_key
{string} Mapping keystring
to lookup corresponding valuestring
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
Retrieves stored value
string
or provided defaultstring
if undefined
Source getOrElse(mapping(string => string) _self, string _key, string _default)
Parameters
-
_self
{mapping(string => string)} Mapping of key/valuestring
pairs -
_key
{string} Mapping keystring
to lookup corresponding valuestring
for -
_default
{string} Value to return if keystring
lookup is undefined
Returns -> {string} Value string
for given key string
or _default
if undefined
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/valuestring
pairs -
_key
{string} Mapping keystring
to lookup corresponding valuestring
for -
_reason
{string} Custom error message to throw if valuestring
is undefined
Returns -> {string} Value for given key string
Throws -> {Error} _reason
if value is undefined
Check if
string
key has a corresponding valuestring
defined
Source has(mapping(string => string) _self, string _key)
Parameters
-
_self
{mapping(string => string)} Mapping of key/valuestring
pairs -
_key
{string} Mapping key to check if valuestring
is defined
Returns -> {bool} true
if value string
is defined, or false
if undefined
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/valuestring
pairs -
_key
{string} Mapping key to set corresponding valuestring
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
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/valuestring
pairs -
_key
{string} Mapping key to set corresponding valuestring
for -
_value
{string} Mapping value to set -
_reason
{string} Custom error message to present if valuestring
is""
Throws -> {Error} _reason
if value is ""
Delete value
string
for given_key
Source remove(mapping(string => string) _self, string _key)
Parameters
-
_self
{mapping(string => string)} Mapping of key/valuestring
pairs -
_key
{string} Mapping key to delete corresponding valuestring
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
Delete value
string
for given_key
Source removeOrError(mapping(string => string) _self, string _key, string _reason)
Parameters
-
_self
{mapping(string => string)} Mapping of key/valuestring
pairs -
_key
{string} Mapping key to delete corresponding valuestring
for -
_reason
{string} Custom error message to throw if valuestring
is undefined
Returns -> {string} Stored value string
for given key string
Throws -> {Error} _reason
if value is undefined
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/valuestring
pairs -
_key
{string} Mapping key to set corresponding valuestring
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
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/valuestring
pairs -
_key
{string} Mapping key to set corresponding valuestring
for -
_value
{string} Mapping value to set -
_reason
{string} Custom error message to present if valuestring
is defined
Throws
-
{Error}
_reason
if value is defined -
{Error}
"LibraryMappingString.setOrError: value cannot be """
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.
Options for contributing to library-mapping-string and solidity-utilities
Start making a Fork of this repository to an account that you have write permissions for.
- Add remote for fork URL. The URL syntax is
[email protected]:<NAME>/<REPO>.git
...
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 setfork
as the default remote, eg.git push -u fork main
however, this will also default thefork
remote for pulling from too! Meaning that pulling updates fromorigin
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.
Thanks for even considering it!
Via Liberapay you may 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.
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.