Solidity library for mapping addresses
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 trufflePerhaps 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-addressNote, source code for this library will be located within the
node_modules/@solidity-utilities/library-mapping-addressdirectory ofyour_projectroot
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 thenode_modulessub-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
LibraryMappingAddressvia Truffle migration similar to the following.
migrations/2_your_contract.jsconst 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); };
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.
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(address => address)type
Warning any value of address(0x0) is treated as null or undefined
Source contracts/LibraryMappingAddress.sol
Retrieves stored value
addressor throws an error if undefined
Source get(mapping(address => address) _self, address _key)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping keyaddressto lookup corresponding valueaddressfor
Returns -> {address} Value for given key address
Throws -> {Error} "LibraryMappingAddress.get: value not defined"
Retrieves stored value
addressor provided defaultaddressif undefined
Source getOrElse(mapping(address => address) _self, address _key, address _default)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping keyaddressto lookup corresponding valueaddressfor -
_default{address} Value to return if keyaddresslookup is undefined
Returns -> {address} Value address for given key address or _default if undefined
Allows for defining custom error reason if value
addressis undefined
Source getOrError(mapping(address => address) _self, address _key, string _reason)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping keyaddressto lookup corresponding valueaddressfor -
_reason{string} Custom error message to throw if valueaddressis undefined
Returns -> {address} Value for given key address
Throws -> {Error} _reason if value is undefined
Check if
addresskey has a corresponding valueaddressdefined
Source has(mapping(address => address) _self, address _key)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping key to check if valueaddressis defined
Returns -> {bool} true if value address is defined, or false if undefined
Store
_valueunder given_keywithout preventing unintentional overwrites
Source overwrite(mapping(address => address) _self, address _key, address _value)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping key to set corresponding valueaddressfor -
_value{address} Mapping value to set
Throws -> {Error} "LibraryMappingAddress.overwrite: value cannot be 0x0"
Store
_valueunder given_keywithout preventing unintentional overwrites
Source overwriteOrError(mapping(address => address) _self, address _key, address _value, string _reason)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping key to set corresponding valueaddressfor -
_value{address} Mapping value to set -
_reason{string} Custom error message to present if valueaddressis0x0
Throws -> {Error} _reason if value is 0x0
Delete value
addressfor given_key
Source remove(mapping(address => address) _self, address _key)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping key to delete corresponding valueaddressfor
Returns -> {address} Value for given key address
Throws -> {Error} "LibraryMappingAddress.remove: value not defined"
Delete value
addressfor given_key
Source removeOrError(mapping(address => address) _self, address _key, string _reason)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping key to delete corresponding valueaddressfor -
_reason{string} Custom error message to throw if valueaddressis undefined
Returns -> {address} Stored value address for given key address
Throws -> {Error} _reason if value is undefined
Store
_valueunder given_keywhile preventing unintentional overwrites
Source set(mapping(address => address) _self, address _key, address _value)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping key to set corresponding valueaddressfor -
_value{address} Mapping value to set
Throws
-
{Error}
"LibraryMappingAddress.set: value already defined" -
{Error}
"LibraryMappingAddress.set: value cannot be 0x0"
Stores
_valueunder given_keywhile preventing unintentional overwrites
Source setOrError(mapping(address => address) _self, address _key, address _value, string _reason)
Parameters
-
_self{mapping(address => address)} Mapping of key/valueaddresspairs -
_key{address} Mapping key to set corresponding valueaddressfor -
_value{address} Mapping value to set -
_reason{string} Custom error message to present if valueaddressis defined
Throws
-
{Error}
_reasonif value is defined -
{Error}
"LibraryMappingAddress.setOrError: value cannot be 0x0"
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.
Options for contributing to library-mapping-address and solidity-utilities
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
[email protected]:<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 [email protected]:<NAME>/library-mapping-address.git
git remote add origin [email protected]:solidity-utilities/library-mapping-address.git- Install development dependencies
cd ~/git/hub/solidity-utilities/library-mapping-address
npm ciNote, the
cioption above is recommended instead ofinstallto avoid mutating thepackage.json, and/orpackage-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.
Methods for financially supporting
solidity-utilitiesthat maintainslibrary-mapping-address
Thanks for even considering it!
Via Liberapay you may
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.
Note, this section only documents breaking changes or major feature releases
Make all functions
externaland prevent setting0x0values
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.
Resources that where helpful in building this project so far.
-
NPM -- Creating and Publishing an Organization Scoped Package
-
Solidity Docs -- Error handling: Assert, Require, Revert and Exceptions
-
StackExchange Ethereum -- How can I reference a deployed library in my solidity contract
-
StackExchange Ethereum -- Start up ganache-cli ethereum client in travis CI for testing
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.