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 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-address
Note, source code for this library will be located within the
node_modules/@solidity-utilities/library-mapping-address
directory ofyour_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 thenode_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); };
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
address
or throws an error if undefined
Source get(mapping(address => address) _self, address _key)
Parameters
-
_self
{mapping(address => address)} Mapping of key/valueaddress
pairs -
_key
{address} Mapping keyaddress
to lookup corresponding valueaddress
for
Returns -> {address} Value for given key address
Throws -> {Error} "LibraryMappingAddress.get: value not defined"
Retrieves stored value
address
or provided defaultaddress
if undefined
Source getOrElse(mapping(address => address) _self, address _key, address _default)
Parameters
-
_self
{mapping(address => address)} Mapping of key/valueaddress
pairs -
_key
{address} Mapping keyaddress
to lookup corresponding valueaddress
for -
_default
{address} Value to return if keyaddress
lookup is undefined
Returns -> {address} Value address
for given key address
or _default
if undefined
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/valueaddress
pairs -
_key
{address} Mapping keyaddress
to lookup corresponding valueaddress
for -
_reason
{string} Custom error message to throw if valueaddress
is undefined
Returns -> {address} Value for given key address
Throws -> {Error} _reason
if value is undefined
Check if
address
key has a corresponding valueaddress
defined
Source has(mapping(address => address) _self, address _key)
Parameters
-
_self
{mapping(address => address)} Mapping of key/valueaddress
pairs -
_key
{address} Mapping key to check if valueaddress
is defined
Returns -> {bool} true
if value address
is defined, or false
if undefined
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/valueaddress
pairs -
_key
{address} Mapping key to set corresponding valueaddress
for -
_value
{address} Mapping value to set
Throws -> {Error} "LibraryMappingAddress.overwrite: value cannot be 0x0"
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/valueaddress
pairs -
_key
{address} Mapping key to set corresponding valueaddress
for -
_value
{address} Mapping value to set -
_reason
{string} Custom error message to present if valueaddress
is0x0
Throws -> {Error} _reason
if value is 0x0
Delete value
address
for given_key
Source remove(mapping(address => address) _self, address _key)
Parameters
-
_self
{mapping(address => address)} Mapping of key/valueaddress
pairs -
_key
{address} Mapping key to delete corresponding valueaddress
for
Returns -> {address} Value for given key address
Throws -> {Error} "LibraryMappingAddress.remove: value not defined"
Delete value
address
for given_key
Source removeOrError(mapping(address => address) _self, address _key, string _reason)
Parameters
-
_self
{mapping(address => address)} Mapping of key/valueaddress
pairs -
_key
{address} Mapping key to delete corresponding valueaddress
for -
_reason
{string} Custom error message to throw if valueaddress
is undefined
Returns -> {address} Stored value address
for given key address
Throws -> {Error} _reason
if value is undefined
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/valueaddress
pairs -
_key
{address} Mapping key to set corresponding valueaddress
for -
_value
{address} Mapping value to set
Throws
-
{Error}
"LibraryMappingAddress.set: value already defined"
-
{Error}
"LibraryMappingAddress.set: value cannot be 0x0"
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/valueaddress
pairs -
_key
{address} Mapping key to set corresponding valueaddress
for -
_value
{address} Mapping value to set -
_reason
{string} Custom error message to present if valueaddress
is defined
Throws
-
{Error}
_reason
if 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
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 ofinstall
to 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-utilities
that 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
external
and prevent setting0x0
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.
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.