/
StringStorage.sol
272 lines (247 loc) · 12 KB
/
StringStorage.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.7;
import { LibraryMappingString } from "@solidity-utilities/library-mapping-string/contracts/LibraryMappingString.sol";
/// @title contract for storing and interacting with key/value string pairs
/// @dev Depends on `@solidity-utilities/library-mapping-string`
/// @author S0AndS0
contract StringStorage {
using LibraryMappingString for mapping(string => string);
/// Store key/value `string` pairs
mapping(string => string) public data;
/// Warning order of indexes **NOT** guaranteed!
mapping(string => uint256) public indexes;
/// Warning order of keys **NOT** guaranteed!
string[] public keys;
/// Allow mutation from specified `address`
address public owner;
/// Allow mutation from specified `address`s
mapping(address => bool) authorized;
/* -------------------------------------------------------------------- */
/// @notice Define instance of `StringStorage`
/// @param _owner **{address}** Account or contract authorized to mutate stored data
constructor(address _owner) {
owner = _owner;
}
/* -------------------------------------------------------------------- */
/// @notice Requires message sender to be an instance owner
/// @param _caller **{string}** Function name that implements this modifier
/// @custom:throws **{Error}** `"StringStorage._caller: message sender not an owner"`
modifier onlyOwner(string memory _caller) {
string memory _message = string(
abi.encodePacked(
"StringStorage.",
_caller,
": message sender not an owner"
)
);
require(msg.sender == owner, _message);
_;
}
/// @notice Requires message sender to be in authorized mapping or contract owner
/// @param _caller **{string}** Function name that implements this modifier
/// @custom:throws **{Error}** `"StringStorage._caller: message sender not authorized"`
modifier onlyAuthorized(string memory _caller) {
string memory _message = string(
abi.encodePacked(
"StringStorage.",
_caller,
": message sender not authorized"
)
);
require(authorized[msg.sender] || msg.sender == owner, _message);
_;
}
/* -------------------------------------------------------------------- */
/// @notice Insert `address` into `mapping` of `authorized` data structure
/// @dev Does not check if `address` is already `authorized`
/// @param _key **{address}** Key to set value of `true`
/// @custom:throws **{Error}** `"StringStorage.addAuthorized: message sender not an owner"`
function addAuthorized(address _key) external onlyOwner("addAuthorized") {
authorized[_key] = true;
}
/// @notice Overwrite old `owner` with new owner `address`
/// @param _new_owner **{address}** New owner address
/// @custom:throws **{Error}** `"StringStorage.changeOwner: message sender not an owner"`
function changeOwner(address _new_owner) external onlyOwner("changeOwner") {
owner = _new_owner;
}
/// @notice Delete `mapping` string key/value pairs and remove all `string` from `keys`
/// @dev **Warning** may fail if storing many `string` pairs
/// @custom:throws **{Error}** `"StringStorage.clear: message sender not an owner"`
function clear() external onlyAuthorized("clear") {
uint256 _index = keys.length;
while (_index > 0) {
_index--;
string memory _key = keys[_index];
data.remove(_key);
delete indexes[_key];
keys.pop();
}
}
/// @notice Remove `address` from `mapping` of `authorized` data structure
/// @param _key **{address}** Key to set value of `false`
/// @custom:throws **{Error}** `"StringStorage.deleteAuthorized: message sender not authorized"`
/// @custom:throws **{Error}** `"StringStorage.deleteAuthorized: cannot remove owner"`
function deleteAuthorized(address _key)
external
onlyAuthorized("deleteAuthorized")
{
require(
msg.sender == owner || msg.sender == _key,
"AddressStorage.deleteAuthorized: message sender not authorized"
);
require(
_key != owner,
"AddressStorage.deleteAuthorized: cannot remove owner"
);
delete authorized[_key];
}
/// @notice Retrieve stored value `string` or throws an error if _undefined_
/// @dev Passes parameter to `data.getOrError` with default Error `_reason` to throw
/// @param _key **{string}** Mapping key `string` to lookup corresponding value `string` for
/// @return **{string}** Value for given key `string`
/// @custom:throws **{Error}** `"StringStorage.get: value not defined"`
function get(string calldata _key) public view returns (string memory) {
return data.getOrError(_key, "StringStorage.get: value not defined");
}
/// @notice Retrieve stored value `string` or provided default `string` if _undefined_
/// @dev Forwards parameters to `data.getOrElse`
/// @param _key **{string}** Mapping key `string` to lookup corresponding value `string` for
/// @param _default **{string}** Value to return if key `string` lookup is _undefined_
/// @return **{string}** Value `string` for given key `string` or `_default` if _undefined_
function getOrElse(string calldata _key, string calldata _default)
external
view
returns (string memory)
{
return data.getOrElse(_key, _default);
}
/// @notice Allow for defining custom error reason if value `string` is _undefined_
/// @dev Forwards parameters to `data.getOrError`
/// @param _key **{string}** Mapping key `string` to lookup corresponding value `string` for
/// @param _reason **{string}** Custom error message to throw if value `string` is _undefined_
/// @return **{string}** Value for given key `string`
/// @custom:throws **{Error}** `_reason` if value is _undefined_
function getOrError(string calldata _key, string memory _reason)
external
view
returns (string memory)
{
return data.getOrError(_key, _reason);
}
/// @notice Check if `string` key has a corresponding value `string` defined
/// @dev Forwards parameter to `data.has`
/// @param _key **{string}** Mapping key to check if value `string` is defined
/// @return **{bool}** `true` if value `string` is defined, or `false` if _undefined_
function has(string calldata _key) public view returns (bool) {
return data.has(_key);
}
/// @notice Index for `string` key within `keys` array
/// @dev Passes parameter to `indexOfOrError` with default `_reason`
/// @param _key **{string}** Key to lookup index for
/// @return **{uint256}** Current index for given `_key` within `keys` array
/// @custom:throws **{Error}** `"StringStorage.indexOf: key not defined"`
function indexOf(string calldata _key) external view returns (uint256) {
return indexOfOrError(_key, "StringStorage.indexOf: key not defined");
}
/// @notice Index for `string` key within `keys` array
/// @dev Cannot depend on results being valid if mutation is allowed between calls
/// @param _key **{string}** Key to lookup index for
/// @param _reason **{string}** Custom error message to throw if value `string` is _undefined_
/// @return **{uint256}** Current index for given `_key` within `keys` array
/// @custom:throws **{Error}** `_reason` if value for `_key` is _undefined_
function indexOfOrError(string calldata _key, string memory _reason)
public
view
returns (uint256)
{
require(data.has(_key), _reason);
return indexes[_key];
}
/// @notice Convenience function to read all `mapping` key strings
/// @dev Cannot depend on results being valid if mutation is allowed between calls
/// @return **{string[]}** Keys `string` array
function listKeys() external view returns (string[] memory) {
return keys;
}
/// @notice Delete value `string` for given `_key`
/// @dev Passes parameter to `removeOrError` with default `_reason`
/// @param _key **{string}** Mapping key to delete corresponding value `string` for
/// @return **{string}** Value `string` that was removed from `data` storage
/// @custom:throws **{Error}** `"StringStorage.remove: message sender not an owner"`
/// @custom:throws **{Error}** `"StringStorage.remove: value not defined"`
function remove(string calldata _key)
public
onlyAuthorized("remove")
returns (string memory)
{
return removeOrError(_key, "StringStorage.remove: value not defined");
}
/// @notice Delete value `string` for given `_key`
/// @dev **Warning** reorders `keys`, and mutates `indexes`, for efficiency reasons
/// @param _key **{string}** Mapping key to delete corresponding value `string` for
/// @param _reason **{string}** Custom error message to throw if value `string` is _undefined_
/// @return **{string}** Value `string` that was removed from `data` storage
/// @custom:throws **{Error}** `"StringStorage.removeOrError: message sender not an owner"`
/// @custom:throws **{Error}** `_reason` if value is _undefined_
function removeOrError(string calldata _key, string memory _reason)
public
onlyAuthorized("removeOrError")
returns (string memory)
{
string memory _value = data.removeOrError(_key, _reason);
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
/// @dev Forwards parameters to `setOrError` with default `_reason`
/// @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
onlyAuthorized("set")
{
setOrError(_key, _value, "StringStorage.set: value already defined");
}
/// @notice Store `_value` under given `_key` while preventing unintentional overwrites
/// @dev Forwards parameters to `data.setOrError`
/// @param _key **{string}** Mapping key to set corresponding value `string` for
/// @param _value **{string}** Mapping value to set
/// @param _reason **{string}** Custom error message to present if value `string` is defined
/// @custom:throws **{Error}** `"StringStorage.setOrError: message sender not an owner"`
/// @custom:throws **{Error}** `_reason` if value is defined
function setOrError(
string calldata _key,
string calldata _value,
string memory _reason
) public onlyAuthorized("setOrError") {
data.setOrError(_key, _value, _reason);
indexes[_key] = keys.length;
keys.push(_key);
}
/// @notice Number of key/value `string` pairs stored
/// @dev Cannot depend on results being valid if mutation is allowed between calls
/// @return **{uint256}** Length of `keys` array
function size() external view returns (uint256) {
return keys.length;
}
}