Skip to content

solidity-utilities/library-mapping-address

Repository files navigation

Library Mapping Address

Solidity library for mapping addresses

Byte size of Library Mapping Address 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-address

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

Solidity contracts may then import code via similar syntax as shown

import {
    LibraryMappingAddress
} from "@solidity-utilities/library-mapping-address/contracts/LibraryMappingAddress.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.


Alternatively this library may be installed via Truffle, eg...

cd your_project

truffle install library-mapping-address

... However, if utilizing Truffle for dependency management Solidity contracts must import code via a slightly different path

import {
    LibraryMappingAddress
} from "library-mapping-address/contracts/LibraryMappingAddress.sol";

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

migrations/2_your_contract.js

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

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

Usage

How to utilize this repository

Write a set of contracts that make use of, and extend, LibraryMappingAddress features.

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

import {
    LibraryMappingAddress
} from "@solidity-utilities/mapped-addresses/contracts/LibraryMappingAddress.sol";

/// @title Example contract to be stored and retrieved by `AccountStorage`
/// @author S0AndS0
contract Account {
    address payable public owner;
    string public name;

    constructor(address payable _owner, string memory _name) {
        owner = _owner;
        name = _name;
    }
}

/// @title Programming interface for storing contracts within custom mapping
/// @author S0AndS0
contract AccountStorage {
    using LibraryMappingAddress for mapping(address => address);
    mapping(address => address) data;
    mapping(address => uint256) indexes;
    address[] 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(
                "AccountStorage.",
                _caller,
                ": Message sender not an owner"
            )
        );
        require(msg.sender == owner, _message);
        _;
    }

    /// @notice Converts `LibraryMappingAddress.get` value `address`
    /// @param _key {address}
    /// @return Account
    /// @custom:throws {Error} `"LibraryMappingAddress.get: value not defined"`
    function get(address _key) public view returns (Account) {
        return Account(data.get(_key));
    }

    /// @notice Presents `LibraryMappingAddress.has` result for stored `data`
    /// @return bool
    function has(address _key) public view returns (bool) {
        return data.has(_key);
    }

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

    /// @notice Converts `LibraryMappingAddress.remove` value `address`
    /// @dev **Warning** Overwrites current key with last key
    /// @return Account
    /// @custom:javascript Returns transaction object
    /// @custom:throws {Error} `"AccountStorage.remove: Message sender not an owner"`
    /// @custom:throws {Error} `"LibraryMappingAddress.remove: value not defined"`
    function remove(address _key) public onlyOwner("remove") returns (Account) {
        address _value = data.removeOrError(
            _key,
            "AccountStorage.remove: value not defined"
        );
        uint256 _last_index = keys.length - 1;
        address _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 Account(_value);
    }

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

    /// @notice Convert `_value` for `LibraryMappingAddress.set`
    /// @param _key {address}
    /// @param _value {Account}
    /// @custom:throws {Error} `"AccountStorage.set: Message sender not an owner"`
    /// @custom:throws {Error} `"LibraryMappingAddress.set: value already defined"`
    function set(address _key, Account _value) public onlyOwner("set") {
        data.set(_key, address(_value));
        keys.push(_key);
    }

    /// @notice Allow callers access to `keys.length`
    /// @return uint256 Length of keys `address` array
    /// @custom:javascript Returns `BN` data object
    function size() public view returns (uint256) {
        return keys.length;
    }
}

Above the AccountStorage contract;

  • maintains a list of keys

  • restricts mutation to owner only

  • converts between references (address) and references from/to Account

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__AccountStorage.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 LibraryMappingAddress

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

Warning any value of address(0x0) is treated as null or undefined

Source contracts/LibraryMappingAddress.sol


Method get

Retrieves stored value address or throws an error if undefined

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

Parameters

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

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

Returns -> {address} Value for given key address

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


Method getOrElse

Retrieves stored value address or provided default address if undefined

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

Parameters

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

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

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

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


Method getOrError

Allows for defining custom error reason if value address is undefined

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

Parameters

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

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

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

Returns -> {address} Value for given key address

Throws -> {Error} _reason if value is undefined


Method has

Check if address key has a corresponding value address defined

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

Parameters

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

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

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


Method overwrite

Store _value under given _key without preventing unintentional overwrites

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

Parameters

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

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

  • _value {address} Mapping value to set

Throws -> {Error} "LibraryMappingAddress.overwrite: value cannot be 0x0"


Method overwriteOrError

Store _value under given _key without preventing unintentional overwrites

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

Parameters

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

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

  • _value {address} Mapping value to set

  • _reason {string} Custom error message to present if value address is 0x0

Throws -> {Error} _reason if value is 0x0


Method remove

Delete value address for given _key

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

Parameters

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

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

Returns -> {address} Value for given key address

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


Method removeOrError

Delete value address for given _key

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

Parameters

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

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

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

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

Throws -> {Error} _reason if value is undefined


Method set

Store _value under given _key while preventing unintentional overwrites

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

Parameters

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

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

  • _value {address} Mapping value to set

Throws

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

  • {Error} "LibraryMappingAddress.set: value cannot be 0x0"


Method setOrError

Stores _value under given _key while preventing unintentional overwrites

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

Parameters

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

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

  • _value {address} Mapping value to set

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

Throws

  • {Error} _reason if value is defined

  • {Error} "LibraryMappingAddress.setOrError: value cannot be 0x0"


Notes

Additional things to keep in mind when developing

Solidity libraries provide methods for a given type only for the contract that is using the library, review the Usage example for details on how to forward library features to external consumers.


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-address and solidity-utilities


Forking

Tips for forking library-mapping-address

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

  • Clone fork URL. The URL syntax is git@github.com:<NAME>/<REPO>.git, then add this repository as a remote...
mkdir -p ~/git/hub/solidity-utilities

cd ~/git/hub/solidity-utilities

git clone --origin fork git@github.com:<NAME>/library-mapping-address.git

git remote add origin git@github.com:solidity-utilities/library-mapping-address.git
  • Install development dependencies
cd ~/git/hub/solidity-utilities/library-mapping-address

npm ci

Note, the ci option above is recommended instead of install to avoid mutating the package.json, and/or package-lock.json, file(s) implicitly

  • Commit your changes and push to your fork, eg. to fix an issue...
cd ~/git/hub/solidity-utilities/library-mapping-address


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


**Edits**


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


git push fork 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

Methods for financially supporting solidity-utilities that maintains library-mapping-address

Thanks for even considering it!

Via Liberapay you may sponsor__shields_io__liberapay on a repeating basis.

For non-repeating contributions Ethereum is accepted via the following public address;

0x5F3567160FF38edD5F32235812503CA179eaCbca

Regardless of if you're able to financially support projects such as library-mapping-address 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.


Change Log

Note, this section only documents breaking changes or major feature releases


Version 0.1.0

Make all functions external and prevent setting 0x0 values

git diff 'v0.1.0' 'v0.0.5'

Developer notes

New error message will be reported if overwrite, overwriteOrError, set, or setOrError receive a _value of 0x0 to define. Please use remove, or removeOrError, to un-set a value. Note, for overwriteOrError and setOrError, this new error message is reported after _reason if the value undefined.

The conversion of public to external type required duplication of code. This is a trade-off for reducing execution stack size, and possible gas fees, which comes at the cost of future development effort.


Attribution

Resources that where helpful in building this project so far.


License

Legal side of Open Source

Solidity library for mapping addresses
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.