1284 lines
42 KiB
Solidity
1284 lines
42 KiB
Solidity
pragma solidity ^0.8.8;
|
|
|
|
interface IERC165 {
|
|
/**
|
|
* @dev Returns true if this contract implements the interface defined by
|
|
* `interfaceId`. See the corresponding [ERC165] standard.
|
|
* to learn more about how these ids are created.
|
|
*
|
|
* This function call must use less than 30 000 gas.
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId) external view returns (bool);
|
|
}
|
|
|
|
contract ERC165 is IERC165 {
|
|
/**
|
|
* @inheritdoc IERC165
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId)
|
|
public
|
|
view
|
|
virtual
|
|
override
|
|
returns (bool)
|
|
{
|
|
return interfaceId == type(IERC165).interfaceId;
|
|
}
|
|
}
|
|
|
|
contract ERC173 {
|
|
address private _owner;
|
|
|
|
event OwnershipTransferred(
|
|
address indexed previousOwner,
|
|
address indexed newOwner
|
|
);
|
|
|
|
/**
|
|
* @dev Initializes the contract setting the deployer as the initial owner.
|
|
*/
|
|
constructor() {
|
|
_transferOwnership(msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @dev Throws if called by any account other than the owner.
|
|
*/
|
|
modifier onlyOwner() {
|
|
_checkOwner();
|
|
_;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the address of the current owner.
|
|
*/
|
|
function owner() public view virtual returns (address) {
|
|
return _owner;
|
|
}
|
|
|
|
/**
|
|
* @dev Throws if the sender is not the owner.
|
|
*/
|
|
function _checkOwner() internal view virtual {
|
|
require(owner() == msg.sender, "Ownable: caller is not the owner");
|
|
}
|
|
|
|
/**
|
|
* @dev Leaves the contract without owner. It will not be possible to call
|
|
* `onlyOwner` functions anymore. Can only be called by the current owner.
|
|
*
|
|
* NOTE: Renouncing ownership will leave the contract without an owner,
|
|
* thereby removing any functionality that is only available to the owner.
|
|
*/
|
|
function renounceOwnership() public virtual onlyOwner {
|
|
_transferOwnership(address(0));
|
|
}
|
|
|
|
/**
|
|
* @dev Transfers ownership of the contract to a new account (`newOwner`).
|
|
* Can only be called by the current owner.
|
|
*/
|
|
function transferOwnership(address newOwner) public virtual onlyOwner {
|
|
require(
|
|
newOwner != address(0),
|
|
"Ownable: new owner is the zero address"
|
|
);
|
|
_transferOwnership(newOwner);
|
|
}
|
|
|
|
/**
|
|
* @dev Transfers ownership of the contract to a new account (`newOwner`).
|
|
* Internal function without access restriction.
|
|
*/
|
|
function _transferOwnership(address newOwner) internal virtual {
|
|
address oldOwner = _owner;
|
|
_owner = newOwner;
|
|
emit OwnershipTransferred(oldOwner, newOwner);
|
|
}
|
|
}
|
|
|
|
interface IERC725X is IERC165 {
|
|
/**
|
|
* @notice Emitted when deploying a contract
|
|
* @param operationType The opcode used to deploy the contract (CREATE or CREATE2)
|
|
* @param contractAddress The created contract address
|
|
* @param value The amount of native tokens (in Wei) sent to fund the created contract address
|
|
* @param salt The salt used in case of CREATE2. Will be bytes32(0) in case of CREATE operation
|
|
*/
|
|
event ContractCreated(
|
|
uint256 indexed operationType,
|
|
address indexed contractAddress,
|
|
uint256 indexed value,
|
|
bytes32 salt
|
|
);
|
|
|
|
/**
|
|
* @notice Emitted when calling an address (EOA or contract)
|
|
* @param operationType The low-level call opcode used to call the `to` address (CALL, STATICALL or DELEGATECALL)
|
|
* @param target The address to call. `target` will be unused if a contract is created (operation types 1 and 2).
|
|
* @param value The amount of native tokens transferred with the call (in Wei)
|
|
* @param selector The first 4 bytes (= function selector) of the data sent with the call
|
|
*/
|
|
event Executed(
|
|
uint256 indexed operationType,
|
|
address indexed target,
|
|
uint256 indexed value,
|
|
bytes4 selector
|
|
);
|
|
|
|
/**
|
|
* @param operationType The operation type used: CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4
|
|
* @param target The address of the EOA or smart contract. (unused if a contract is created via operation type 1 or 2)
|
|
* @param value The amount of native tokens to transfer (in Wei)
|
|
* @param data The call data, or the creation bytecode of the contract to deploy
|
|
*
|
|
* @dev Generic executor function to:
|
|
*
|
|
* - send native tokens to any address.
|
|
* - interact with any contract by passing an abi-encoded function call in the `data` parameter.
|
|
* - deploy a contract by providing its creation bytecode in the `data` parameter.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - SHOULD only be callable by the owner of the contract set via ERC173.
|
|
* - if a `value` is provided, the contract MUST have at least this amount in its balance to execute successfully.
|
|
* - if the operation type is STATICCALL or DELEGATECALL, `value` SHOULD be 0.
|
|
* - `target` SHOULD be address(0) when deploying a contract.
|
|
*
|
|
* Emits an {Executed} event, when a call is made with `operationType` 0 (CALL), 3 (STATICCALL) or 4 (DELEGATECALL)
|
|
* Emits a {ContractCreated} event, when deploying a contract with `operationType` 1 (CREATE) or 2 (CREATE2)
|
|
*/
|
|
function execute(
|
|
uint256 operationType,
|
|
address target,
|
|
uint256 value,
|
|
bytes memory data
|
|
) external payable returns (bytes memory);
|
|
|
|
/**
|
|
* @param operationsType The list of operations type used: CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4
|
|
* @param targets The list of addresses to call. `targets` will be unused if a contract is created (operation types 1 and 2).
|
|
* @param values The list of native token amounts to transfer (in Wei)
|
|
* @param datas The list of call data, or the creation bytecode of the contract to deploy
|
|
*
|
|
* @dev Generic batch executor function to:
|
|
*
|
|
* - send native tokens to any address.
|
|
* - interact with any contract by passing an abi-encoded function call in the `datas` parameter.
|
|
* - deploy a contract by providing its creation bytecode in the `datas` parameter.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - The length of the parameters provided MUST be equal
|
|
* - SHOULD only be callable by the owner of the contract set via ERC173.
|
|
* - if a `values` is provided, the contract MUST have at least this amount in its balance to execute successfully.
|
|
* - if the operation type is STATICCALL or DELEGATECALL, `values` SHOULD be 0.
|
|
* - `targets` SHOULD be address(0) when deploying a contract.
|
|
*
|
|
* Emits an {Executed} event, when a call is made with `operationType` 0 (CALL), 3 (STATICCALL) or 4 (DELEGATECALL)
|
|
* Emits a {ContractCreated} event, when deploying a contract with `operationType` 1 (CREATE) or 2 (CREATE2)
|
|
*/
|
|
function execute(
|
|
uint256[] memory operationsType,
|
|
address[] memory targets,
|
|
uint256[] memory values,
|
|
bytes[] memory datas
|
|
) external payable returns (bytes[] memory);
|
|
}
|
|
|
|
// ERC165 INTERFACE IDs
|
|
bytes4 constant _INTERFACEID_ERC725X = 0x570ef073;
|
|
|
|
// ERC725X OPERATION TYPES
|
|
uint256 constant OPERATION_0_CALL = 0;
|
|
uint256 constant OPERATION_1_CREATE = 1;
|
|
uint256 constant OPERATION_2_CREATE2 = 2;
|
|
uint256 constant OPERATION_3_STATICCALL = 3;
|
|
uint256 constant OPERATION_4_DELEGATECALL = 4;
|
|
|
|
/**
|
|
* @dev reverts when trying to send more native tokens `value` than available in current `balance`.
|
|
* @param balance the balance of the ERC725X contract.
|
|
* @param value the amount of native tokens sent via `ERC725X.execute(...)`.
|
|
*/
|
|
error ERC725X_InsufficientBalance(uint256 balance, uint256 value);
|
|
|
|
/**
|
|
* @dev reverts when the `operationTypeProvided` is none of the default operation types available.
|
|
* (CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4)
|
|
*/
|
|
error ERC725X_UnknownOperationType(uint256 operationTypeProvided);
|
|
|
|
/**
|
|
* @dev the `value` parameter (= sending native tokens) is not allowed when making a staticcall
|
|
* via `ERC725X.execute(...)` because sending native tokens is a state changing operation.
|
|
*/
|
|
error ERC725X_MsgValueDisallowedInStaticCall();
|
|
|
|
/**
|
|
* @dev the `value` parameter (= sending native tokens) is not allowed when making a delegatecall
|
|
* via `ERC725X.execute(...)` because msg.value is persisting.
|
|
*/
|
|
error ERC725X_MsgValueDisallowedInDelegateCall();
|
|
|
|
/**
|
|
* @dev reverts when passing a `to` address while deploying a contract va `ERC725X.execute(...)`
|
|
* whether using operation type 1 (CREATE) or 2 (CREATE2).
|
|
*/
|
|
error ERC725X_CreateOperationsRequireEmptyRecipientAddress();
|
|
|
|
/**
|
|
* @dev reverts when contract deployment via `ERC725X.execute(...)` failed.
|
|
* whether using operation type 1 (CREATE) or 2 (CREATE2).
|
|
*/
|
|
error ERC725X_ContractDeploymentFailed();
|
|
|
|
/**
|
|
* @dev reverts when no contract bytecode was provided as parameter when trying to deploy a contract
|
|
* via `ERC725X.execute(...)`, whether using operation type 1 (CREATE) or 2 (CREATE2).
|
|
*/
|
|
error ERC725X_NoContractBytecodeProvided();
|
|
|
|
/**
|
|
* @dev reverts when there is not the same number of operation, to addresses, value, and data.
|
|
*/
|
|
error ERC725X_ExecuteParametersLengthMismatch();
|
|
|
|
contract ERC725X is ERC173, ERC165, IERC725X {
|
|
/**
|
|
* @inheritdoc ERC165
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId)
|
|
public
|
|
view
|
|
virtual
|
|
override(IERC165, ERC165)
|
|
returns (bool)
|
|
{
|
|
return
|
|
interfaceId == _INTERFACEID_ERC725X ||
|
|
super.supportsInterface(interfaceId);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IERC725X
|
|
*/
|
|
function execute(
|
|
uint256 operationType,
|
|
address target,
|
|
uint256 value,
|
|
bytes memory data
|
|
) public payable virtual onlyOwner returns (bytes memory) {
|
|
if (address(this).balance < value) {
|
|
revert ERC725X_InsufficientBalance(address(this).balance, value);
|
|
}
|
|
return _execute(operationType, target, value, data);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IERC725X
|
|
*/
|
|
function execute(
|
|
uint256[] memory operationsType,
|
|
address[] memory targets,
|
|
uint256[] memory values,
|
|
bytes[] memory datas
|
|
) public payable virtual onlyOwner returns (bytes[] memory result) {
|
|
if (
|
|
operationsType.length != targets.length ||
|
|
(targets.length != values.length || values.length != datas.length)
|
|
) revert ERC725X_ExecuteParametersLengthMismatch();
|
|
|
|
result = new bytes[](operationsType.length);
|
|
for (uint256 i = 0; i < operationsType.length; i++) {
|
|
if (address(this).balance < values[i])
|
|
revert ERC725X_InsufficientBalance(
|
|
address(this).balance,
|
|
values[i]
|
|
);
|
|
|
|
result[i] = _execute(
|
|
operationsType[i],
|
|
targets[i],
|
|
values[i],
|
|
datas[i]
|
|
);
|
|
}
|
|
}
|
|
|
|
function _execute(
|
|
uint256 operationType,
|
|
address target,
|
|
uint256 value,
|
|
bytes memory data
|
|
) internal virtual returns (bytes memory) {
|
|
// CALL
|
|
if (operationType == OPERATION_0_CALL) {
|
|
return _executeCall(target, value, data);
|
|
}
|
|
|
|
// Deploy with CREATE
|
|
if (operationType == uint256(OPERATION_1_CREATE)) {
|
|
if (target != address(0))
|
|
revert ERC725X_CreateOperationsRequireEmptyRecipientAddress();
|
|
return _deployCreate(value, data);
|
|
}
|
|
|
|
// Deploy with CREATE2
|
|
if (operationType == uint256(OPERATION_2_CREATE2)) {
|
|
if (target != address(0))
|
|
revert ERC725X_CreateOperationsRequireEmptyRecipientAddress();
|
|
return _deployCreate2(value, data);
|
|
}
|
|
|
|
// STATICCALL
|
|
if (operationType == uint256(OPERATION_3_STATICCALL)) {
|
|
if (value != 0) revert ERC725X_MsgValueDisallowedInStaticCall();
|
|
return _executeStaticCall(target, data);
|
|
}
|
|
|
|
// DELEGATECALL
|
|
//
|
|
// WARNING! delegatecall is a dangerous operation type! use with EXTRA CAUTION
|
|
//
|
|
// delegate allows to call another deployed contract and use its functions
|
|
// to update the state of the current calling contract.
|
|
//
|
|
// this can lead to unexpected behaviour on the contract storage, such as:
|
|
// - updating any state variables (even if these are protected)
|
|
// - update the contract owner
|
|
// - run selfdestruct in the context of this contract
|
|
//
|
|
if (operationType == uint256(OPERATION_4_DELEGATECALL)) {
|
|
if (value != 0) revert ERC725X_MsgValueDisallowedInDelegateCall();
|
|
return _executeDelegateCall(target, data);
|
|
}
|
|
|
|
revert ERC725X_UnknownOperationType(operationType);
|
|
}
|
|
|
|
/**
|
|
* @dev perform low-level call (operation type = 0)
|
|
* @param target The address on which call is executed
|
|
* @param value The value to be sent with the call
|
|
* @param data The data to be sent with the call
|
|
* @return result The data from the call
|
|
*/
|
|
function _executeCall(
|
|
address target,
|
|
uint256 value,
|
|
bytes memory data
|
|
) internal virtual returns (bytes memory result) {
|
|
emit Executed(OPERATION_0_CALL, target, value, bytes4(data));
|
|
|
|
// solhint-disable avoid-low-level-calls
|
|
(bool success, bytes memory returnData) = target.call{value: value}(
|
|
data
|
|
);
|
|
result = Address.verifyCallResult(
|
|
success,
|
|
returnData,
|
|
"ERC725X: Unknown Error"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev perform low-level staticcall (operation type = 3)
|
|
* @param target The address on which staticcall is executed
|
|
* @param data The data to be sent with the staticcall
|
|
* @return result The data returned from the staticcall
|
|
*/
|
|
function _executeStaticCall(address target, bytes memory data)
|
|
internal
|
|
virtual
|
|
returns (bytes memory result)
|
|
{
|
|
emit Executed(OPERATION_3_STATICCALL, target, 0, bytes4(data));
|
|
|
|
// solhint-disable avoid-low-level-calls
|
|
(bool success, bytes memory returnData) = target.staticcall(data);
|
|
result = Address.verifyCallResult(
|
|
success,
|
|
returnData,
|
|
"ERC725X: Unknown Error"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev perform low-level delegatecall (operation type = 4)
|
|
* @param target The address on which delegatecall is executed
|
|
* @param data The data to be sent with the delegatecall
|
|
* @return result The data returned from the delegatecall
|
|
*/
|
|
function _executeDelegateCall(address target, bytes memory data)
|
|
internal
|
|
virtual
|
|
returns (bytes memory result)
|
|
{
|
|
emit Executed(OPERATION_4_DELEGATECALL, target, 0, bytes4(data));
|
|
|
|
// solhint-disable avoid-low-level-calls
|
|
(bool success, bytes memory returnData) = target.delegatecall(data);
|
|
result = Address.verifyCallResult(
|
|
success,
|
|
returnData,
|
|
"ERC725X: Unknown Error"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev deploy a contract using the CREATE opcode (operation type = 1)
|
|
* @param value The value to be sent to the contract created
|
|
* @param creationCode The contract creation bytecode to deploy appended with the constructor argument(s)
|
|
* @return newContract The address of the contract created as bytes
|
|
*/
|
|
function _deployCreate(uint256 value, bytes memory creationCode)
|
|
internal
|
|
virtual
|
|
returns (bytes memory newContract)
|
|
{
|
|
if (creationCode.length == 0) {
|
|
revert ERC725X_NoContractBytecodeProvided();
|
|
}
|
|
|
|
address contractAddress;
|
|
// solhint-disable no-inline-assembly
|
|
assembly {
|
|
contractAddress := create(
|
|
value,
|
|
add(creationCode, 0x20),
|
|
mload(creationCode)
|
|
)
|
|
}
|
|
|
|
if (contractAddress == address(0)) {
|
|
revert ERC725X_ContractDeploymentFailed();
|
|
}
|
|
|
|
newContract = abi.encodePacked(contractAddress);
|
|
emit ContractCreated(
|
|
OPERATION_1_CREATE,
|
|
contractAddress,
|
|
value,
|
|
bytes32(0)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev deploy a contract using the CREATE2 opcode (operation type = 2)
|
|
* @param value The value to be sent to the contract created
|
|
* @param creationCode The contract creation bytecode to deploy appended with the constructor argument(s) and a bytes32 salt
|
|
* @return newContract The address of the contract created as bytes
|
|
*/
|
|
function _deployCreate2(uint256 value, bytes memory creationCode)
|
|
internal
|
|
virtual
|
|
returns (bytes memory newContract)
|
|
{
|
|
bytes32 salt = BytesLib.toBytes32(
|
|
creationCode,
|
|
creationCode.length - 32
|
|
);
|
|
bytes memory bytecode = BytesLib.slice(
|
|
creationCode,
|
|
0,
|
|
creationCode.length - 32
|
|
);
|
|
|
|
address contractAddress;
|
|
require(
|
|
address(this).balance >= value,
|
|
"Create2: insufficient balance"
|
|
);
|
|
require(creationCode.length != 0, "Create2: bytecode length is zero");
|
|
/// @solidity memory-safe-assembly
|
|
assembly {
|
|
contractAddress := create2(
|
|
value,
|
|
add(bytecode, 0x20),
|
|
mload(bytecode),
|
|
salt
|
|
)
|
|
}
|
|
require(contractAddress != address(0), "Create2: Failed on deploy");
|
|
|
|
newContract = abi.encodePacked(contractAddress);
|
|
emit ContractCreated(OPERATION_2_CREATE2, contractAddress, value, salt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @title The interface for ERC725Y General data key/value store
|
|
* @dev ERC725Y provides the ability to set arbitrary data key/value pairs that can be changed over time
|
|
* It is intended to standardise certain data key/value pairs to allow automated read and writes
|
|
* from/to the contract storage
|
|
*/
|
|
interface IERC725Y is IERC165 {
|
|
/**
|
|
* @notice Emitted when data at a key is changed
|
|
* @param dataKey The data key which data value is set
|
|
* @param dataValue The data value to set
|
|
*/
|
|
event DataChanged(bytes32 indexed dataKey, bytes dataValue);
|
|
|
|
/**
|
|
* @notice Gets singular data at a given `dataKey`
|
|
* @param dataKey The key which value to retrieve
|
|
* @return dataValue The data stored at the key
|
|
*/
|
|
function getData(bytes32 dataKey)
|
|
external
|
|
view
|
|
returns (bytes memory dataValue);
|
|
|
|
/**
|
|
* @notice Gets array of data for multiple given keys
|
|
* @param dataKeys The array of keys which values to retrieve
|
|
* @return dataValues The array of data stored at multiple keys
|
|
*/
|
|
function getData(bytes32[] memory dataKeys)
|
|
external
|
|
view
|
|
returns (bytes[] memory dataValues);
|
|
|
|
/**
|
|
* @notice Sets singular data for a given `dataKey`
|
|
* @param dataKey The key to retrieve stored value
|
|
* @param dataValue The value to set
|
|
* SHOULD only be callable by the owner of the contract set via ERC173
|
|
*
|
|
* Emits a {DataChanged} event.
|
|
*/
|
|
function setData(bytes32 dataKey, bytes memory dataValue) external;
|
|
|
|
/**
|
|
* @param dataKeys The array of data keys for values to set
|
|
* @param dataValues The array of values to set
|
|
* @dev Sets array of data for multiple given `dataKeys`
|
|
* SHOULD only be callable by the owner of the contract set via ERC173
|
|
*
|
|
* Emits a {DataChanged} event.
|
|
*/
|
|
function setData(bytes32[] memory dataKeys, bytes[] memory dataValues)
|
|
external;
|
|
}
|
|
|
|
// ERC165 INTERFACE IDs
|
|
bytes4 constant _INTERFACEID_ERC725Y = 0x714df77c;
|
|
|
|
/**
|
|
* @dev reverts when there is not the same number of elements in the lists of data keys and data values
|
|
* when calling setData(bytes32[],bytes[]).
|
|
* @param dataKeysLength the number of data keys in the bytes32[] dataKeys
|
|
* @param dataValuesLength the number of data value in the bytes[] dataValue
|
|
*/
|
|
error ERC725Y_DataKeysValuesLengthMismatch(
|
|
uint256 dataKeysLength,
|
|
uint256 dataValuesLength
|
|
);
|
|
|
|
contract ERC725Y is ERC173, ERC165, IERC725Y {
|
|
// overrides
|
|
|
|
/**
|
|
* @inheritdoc ERC165
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId)
|
|
public
|
|
view
|
|
virtual
|
|
override(IERC165, ERC165)
|
|
returns (bool)
|
|
{
|
|
return
|
|
interfaceId == _INTERFACEID_ERC725Y ||
|
|
super.supportsInterface(interfaceId);
|
|
}
|
|
|
|
/**
|
|
* @dev Map the dataKeys to their dataValues
|
|
*/
|
|
mapping(bytes32 => bytes) internal _store;
|
|
|
|
/**
|
|
* @inheritdoc IERC725Y
|
|
*/
|
|
function getData(bytes32 dataKey)
|
|
public
|
|
view
|
|
virtual
|
|
returns (bytes memory dataValue)
|
|
{
|
|
dataValue = _getData(dataKey);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IERC725Y
|
|
*/
|
|
function getData(bytes32[] memory dataKeys)
|
|
public
|
|
view
|
|
virtual
|
|
returns (bytes[] memory dataValues)
|
|
{
|
|
dataValues = new bytes[](dataKeys.length);
|
|
|
|
for (uint256 i = 0; i < dataKeys.length; i++) {
|
|
dataValues[i] = _getData(dataKeys[i]);
|
|
}
|
|
|
|
return dataValues;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IERC725Y
|
|
*/
|
|
function setData(bytes32 dataKey, bytes memory dataValue)
|
|
public
|
|
virtual
|
|
onlyOwner
|
|
{
|
|
_setData(dataKey, dataValue);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IERC725Y
|
|
*/
|
|
function setData(bytes32[] memory dataKeys, bytes[] memory dataValues)
|
|
public
|
|
virtual
|
|
onlyOwner
|
|
{
|
|
if (dataKeys.length != dataValues.length) {
|
|
revert ERC725Y_DataKeysValuesLengthMismatch(
|
|
dataKeys.length,
|
|
dataValues.length
|
|
);
|
|
}
|
|
|
|
for (uint256 i = 0; i < dataKeys.length; i++) {
|
|
_setData(dataKeys[i], dataValues[i]);
|
|
}
|
|
}
|
|
|
|
function _getData(bytes32 dataKey)
|
|
internal
|
|
view
|
|
virtual
|
|
returns (bytes memory dataValue)
|
|
{
|
|
return _store[dataKey];
|
|
}
|
|
|
|
function _setData(bytes32 dataKey, bytes memory dataValue)
|
|
internal
|
|
virtual
|
|
{
|
|
_store[dataKey] = dataValue;
|
|
emit DataChanged(dataKey, dataValue);
|
|
}
|
|
}
|
|
|
|
contract ERC725 is ERC725X, ERC725Y {
|
|
/**
|
|
* @inheritdoc ERC165
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId)
|
|
public
|
|
view
|
|
virtual
|
|
override(ERC725X, ERC725Y)
|
|
returns (bool)
|
|
{
|
|
return
|
|
interfaceId == _INTERFACEID_ERC725X ||
|
|
interfaceId == _INTERFACEID_ERC725Y ||
|
|
super.supportsInterface(interfaceId);
|
|
}
|
|
}
|
|
|
|
// external needed libraries
|
|
|
|
library BytesLib {
|
|
function concat(bytes memory _preBytes, bytes memory _postBytes)
|
|
internal
|
|
pure
|
|
returns (bytes memory)
|
|
{
|
|
bytes memory tempBytes;
|
|
|
|
assembly {
|
|
// Get a location of some free memory and store it in tempBytes as
|
|
// Solidity does for memory variables.
|
|
tempBytes := mload(0x40)
|
|
|
|
// Store the length of the first bytes array at the beginning of
|
|
// the memory for tempBytes.
|
|
let length := mload(_preBytes)
|
|
mstore(tempBytes, length)
|
|
|
|
// Maintain a memory counter for the current write location in the
|
|
// temp bytes array by adding the 32 bytes for the array length to
|
|
// the starting location.
|
|
let mc := add(tempBytes, 0x20)
|
|
// Stop copying when the memory counter reaches the length of the
|
|
// first bytes array.
|
|
let end := add(mc, length)
|
|
|
|
for {
|
|
// Initialize a copy counter to the start of the _preBytes data,
|
|
// 32 bytes into its memory.
|
|
let cc := add(_preBytes, 0x20)
|
|
} lt(mc, end) {
|
|
// Increase both counters by 32 bytes each iteration.
|
|
mc := add(mc, 0x20)
|
|
cc := add(cc, 0x20)
|
|
} {
|
|
// Write the _preBytes data into the tempBytes memory 32 bytes
|
|
// at a time.
|
|
mstore(mc, mload(cc))
|
|
}
|
|
|
|
// Add the length of _postBytes to the current length of tempBytes
|
|
// and store it as the new length in the first 32 bytes of the
|
|
// tempBytes memory.
|
|
length := mload(_postBytes)
|
|
mstore(tempBytes, add(length, mload(tempBytes)))
|
|
|
|
// Move the memory counter back from a multiple of 0x20 to the
|
|
// actual end of the _preBytes data.
|
|
mc := end
|
|
// Stop copying when the memory counter reaches the new combined
|
|
// length of the arrays.
|
|
end := add(mc, length)
|
|
|
|
for {
|
|
let cc := add(_postBytes, 0x20)
|
|
} lt(mc, end) {
|
|
mc := add(mc, 0x20)
|
|
cc := add(cc, 0x20)
|
|
} {
|
|
mstore(mc, mload(cc))
|
|
}
|
|
|
|
// Update the free-memory pointer by padding our last write location
|
|
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
|
|
// next 32 byte block, then round down to the nearest multiple of
|
|
// 32. If the sum of the length of the two arrays is zero then add
|
|
// one before rounding down to leave a blank 32 bytes (the length block with 0).
|
|
mstore(
|
|
0x40,
|
|
and(
|
|
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
|
|
not(31) // Round down to the nearest 32 bytes.
|
|
)
|
|
)
|
|
}
|
|
|
|
return tempBytes;
|
|
}
|
|
|
|
function concatStorage(bytes storage _preBytes, bytes memory _postBytes)
|
|
internal
|
|
{
|
|
assembly {
|
|
// Read the first 32 bytes of _preBytes storage, which is the length
|
|
// of the array. (We don't need to use the offset into the slot
|
|
// because arrays use the entire slot.)
|
|
let fslot := sload(_preBytes.slot)
|
|
// Arrays of 31 bytes or less have an even value in their slot,
|
|
// while longer arrays have an odd value. The actual length is
|
|
// the slot divided by two for odd values, and the lowest order
|
|
// byte divided by two for even values.
|
|
// If the slot is even, bitwise and the slot with 255 and divide by
|
|
// two to get the length. If the slot is odd, bitwise and the slot
|
|
// with -1 and divide by two.
|
|
let slength := div(
|
|
and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),
|
|
2
|
|
)
|
|
let mlength := mload(_postBytes)
|
|
let newlength := add(slength, mlength)
|
|
// slength can contain both the length and contents of the array
|
|
// if length < 32 bytes so let's prepare for that
|
|
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
|
|
switch add(lt(slength, 32), lt(newlength, 32))
|
|
case 2 {
|
|
// Since the new array still fits in the slot, we just need to
|
|
// update the contents of the slot.
|
|
// uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
|
|
sstore(
|
|
_preBytes.slot,
|
|
// all the modifications to the slot are inside this
|
|
// next block
|
|
add(
|
|
// we can just add to the slot contents because the
|
|
// bytes we want to change are the LSBs
|
|
fslot,
|
|
add(
|
|
mul(
|
|
div(
|
|
// load the bytes from memory
|
|
mload(add(_postBytes, 0x20)),
|
|
// zero all bytes to the right
|
|
exp(0x100, sub(32, mlength))
|
|
),
|
|
// and now shift left the number of bytes to
|
|
// leave space for the length in the slot
|
|
exp(0x100, sub(32, newlength))
|
|
),
|
|
// increase length by the double of the memory
|
|
// bytes length
|
|
mul(mlength, 2)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
case 1 {
|
|
// The stored value fits in the slot, but the combined value
|
|
// will exceed it.
|
|
// get the keccak hash to get the contents of the array
|
|
mstore(0x0, _preBytes.slot)
|
|
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
|
|
|
|
// save new length
|
|
sstore(_preBytes.slot, add(mul(newlength, 2), 1))
|
|
|
|
// The contents of the _postBytes array start 32 bytes into
|
|
// the structure. Our first read should obtain the `submod`
|
|
// bytes that can fit into the unused space in the last word
|
|
// of the stored array. To get this, we read 32 bytes starting
|
|
// from `submod`, so the data we read overlaps with the array
|
|
// contents by `submod` bytes. Masking the lowest-order
|
|
// `submod` bytes allows us to add that value directly to the
|
|
// stored value.
|
|
|
|
let submod := sub(32, slength)
|
|
let mc := add(_postBytes, submod)
|
|
let end := add(_postBytes, mlength)
|
|
let mask := sub(exp(0x100, submod), 1)
|
|
|
|
sstore(
|
|
sc,
|
|
add(
|
|
and(
|
|
fslot,
|
|
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
|
|
),
|
|
and(mload(mc), mask)
|
|
)
|
|
)
|
|
|
|
for {
|
|
mc := add(mc, 0x20)
|
|
sc := add(sc, 1)
|
|
} lt(mc, end) {
|
|
sc := add(sc, 1)
|
|
mc := add(mc, 0x20)
|
|
} {
|
|
sstore(sc, mload(mc))
|
|
}
|
|
|
|
mask := exp(0x100, sub(mc, end))
|
|
|
|
sstore(sc, mul(div(mload(mc), mask), mask))
|
|
}
|
|
default {
|
|
// get the keccak hash to get the contents of the array
|
|
mstore(0x0, _preBytes.slot)
|
|
// Start copying to the last used word of the stored array.
|
|
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
|
|
|
|
// save new length
|
|
sstore(_preBytes.slot, add(mul(newlength, 2), 1))
|
|
|
|
// Copy over the first `submod` bytes of the new data as in
|
|
// case 1 above.
|
|
let slengthmod := mod(slength, 32)
|
|
let mlengthmod := mod(mlength, 32)
|
|
let submod := sub(32, slengthmod)
|
|
let mc := add(_postBytes, submod)
|
|
let end := add(_postBytes, mlength)
|
|
let mask := sub(exp(0x100, submod), 1)
|
|
|
|
sstore(sc, add(sload(sc), and(mload(mc), mask)))
|
|
|
|
for {
|
|
sc := add(sc, 1)
|
|
mc := add(mc, 0x20)
|
|
} lt(mc, end) {
|
|
sc := add(sc, 1)
|
|
mc := add(mc, 0x20)
|
|
} {
|
|
sstore(sc, mload(mc))
|
|
}
|
|
|
|
mask := exp(0x100, sub(mc, end))
|
|
|
|
sstore(sc, mul(div(mload(mc), mask), mask))
|
|
}
|
|
}
|
|
}
|
|
|
|
function slice(
|
|
bytes memory _bytes,
|
|
uint256 _start,
|
|
uint256 _length
|
|
) internal pure returns (bytes memory) {
|
|
require(_length + 31 >= _length, "slice_overflow");
|
|
require(_bytes.length >= _start + _length, "slice_outOfBounds");
|
|
|
|
bytes memory tempBytes;
|
|
|
|
assembly {
|
|
switch iszero(_length)
|
|
case 0 {
|
|
// Get a location of some free memory and store it in tempBytes as
|
|
// Solidity does for memory variables.
|
|
tempBytes := mload(0x40)
|
|
|
|
// The first word of the slice result is potentially a partial
|
|
// word read from the original array. To read it, we calculate
|
|
// the length of that partial word and start copying that many
|
|
// bytes into the array. The first word we copy will start with
|
|
// data we don't care about, but the last `lengthmod` bytes will
|
|
// land at the beginning of the contents of the new array. When
|
|
// we're done copying, we overwrite the full first word with
|
|
// the actual length of the slice.
|
|
let lengthmod := and(_length, 31)
|
|
|
|
// The multiplication in the next line is necessary
|
|
// because when slicing multiples of 32 bytes (lengthmod == 0)
|
|
// the following copy loop was copying the origin's length
|
|
// and then ending prematurely not copying everything it should.
|
|
let mc := add(
|
|
add(tempBytes, lengthmod),
|
|
mul(0x20, iszero(lengthmod))
|
|
)
|
|
let end := add(mc, _length)
|
|
|
|
for {
|
|
// The multiplication in the next line has the same exact purpose
|
|
// as the one above.
|
|
let cc := add(
|
|
add(
|
|
add(_bytes, lengthmod),
|
|
mul(0x20, iszero(lengthmod))
|
|
),
|
|
_start
|
|
)
|
|
} lt(mc, end) {
|
|
mc := add(mc, 0x20)
|
|
cc := add(cc, 0x20)
|
|
} {
|
|
mstore(mc, mload(cc))
|
|
}
|
|
|
|
mstore(tempBytes, _length)
|
|
|
|
//update free-memory pointer
|
|
//allocating the array padded to 32 bytes like the compiler does now
|
|
mstore(0x40, and(add(mc, 31), not(31)))
|
|
}
|
|
//if we want a zero-length slice let's just return a zero-length array
|
|
default {
|
|
tempBytes := mload(0x40)
|
|
//zero out the 32 bytes slice we are about to return
|
|
//we need to do it because Solidity does not garbage collect
|
|
mstore(tempBytes, 0)
|
|
|
|
mstore(0x40, add(tempBytes, 0x20))
|
|
}
|
|
}
|
|
|
|
return tempBytes;
|
|
}
|
|
|
|
function toAddress(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (address)
|
|
{
|
|
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
|
|
address tempAddress;
|
|
|
|
assembly {
|
|
tempAddress := div(
|
|
mload(add(add(_bytes, 0x20), _start)),
|
|
0x1000000000000000000000000
|
|
)
|
|
}
|
|
|
|
return tempAddress;
|
|
}
|
|
|
|
function toUint8(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (uint8)
|
|
{
|
|
require(_bytes.length >= _start + 1, "toUint8_outOfBounds");
|
|
uint8 tempUint;
|
|
|
|
assembly {
|
|
tempUint := mload(add(add(_bytes, 0x1), _start))
|
|
}
|
|
|
|
return tempUint;
|
|
}
|
|
|
|
function toUint16(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (uint16)
|
|
{
|
|
require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
|
|
uint16 tempUint;
|
|
|
|
assembly {
|
|
tempUint := mload(add(add(_bytes, 0x2), _start))
|
|
}
|
|
|
|
return tempUint;
|
|
}
|
|
|
|
function toUint32(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (uint32)
|
|
{
|
|
require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
|
|
uint32 tempUint;
|
|
|
|
assembly {
|
|
tempUint := mload(add(add(_bytes, 0x4), _start))
|
|
}
|
|
|
|
return tempUint;
|
|
}
|
|
|
|
function toUint64(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (uint64)
|
|
{
|
|
require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
|
|
uint64 tempUint;
|
|
|
|
assembly {
|
|
tempUint := mload(add(add(_bytes, 0x8), _start))
|
|
}
|
|
|
|
return tempUint;
|
|
}
|
|
|
|
function toUint96(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (uint96)
|
|
{
|
|
require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
|
|
uint96 tempUint;
|
|
|
|
assembly {
|
|
tempUint := mload(add(add(_bytes, 0xc), _start))
|
|
}
|
|
|
|
return tempUint;
|
|
}
|
|
|
|
function toUint128(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (uint128)
|
|
{
|
|
require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
|
|
uint128 tempUint;
|
|
|
|
assembly {
|
|
tempUint := mload(add(add(_bytes, 0x10), _start))
|
|
}
|
|
|
|
return tempUint;
|
|
}
|
|
|
|
function toUint256(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (uint256)
|
|
{
|
|
require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
|
|
uint256 tempUint;
|
|
|
|
assembly {
|
|
tempUint := mload(add(add(_bytes, 0x20), _start))
|
|
}
|
|
|
|
return tempUint;
|
|
}
|
|
|
|
function toBytes32(bytes memory _bytes, uint256 _start)
|
|
internal
|
|
pure
|
|
returns (bytes32)
|
|
{
|
|
require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
|
|
bytes32 tempBytes32;
|
|
|
|
assembly {
|
|
tempBytes32 := mload(add(add(_bytes, 0x20), _start))
|
|
}
|
|
|
|
return tempBytes32;
|
|
}
|
|
|
|
function equal(bytes memory _preBytes, bytes memory _postBytes)
|
|
internal
|
|
pure
|
|
returns (bool)
|
|
{
|
|
bool success = true;
|
|
|
|
assembly {
|
|
let length := mload(_preBytes)
|
|
|
|
// if lengths don't match the arrays are not equal
|
|
switch eq(length, mload(_postBytes))
|
|
case 1 {
|
|
// cb is a circuit breaker in the for loop since there's
|
|
// no said feature for inline assembly loops
|
|
// cb = 1 - don't breaker
|
|
// cb = 0 - break
|
|
let cb := 1
|
|
|
|
let mc := add(_preBytes, 0x20)
|
|
let end := add(mc, length)
|
|
|
|
for {
|
|
let cc := add(_postBytes, 0x20)
|
|
// the next line is the loop condition:
|
|
// while(uint256(mc < end) + cb == 2)
|
|
} eq(add(lt(mc, end), cb), 2) {
|
|
mc := add(mc, 0x20)
|
|
cc := add(cc, 0x20)
|
|
} {
|
|
// if any of these checks fails then arrays are not equal
|
|
if iszero(eq(mload(mc), mload(cc))) {
|
|
// unsuccess:
|
|
success := 0
|
|
cb := 0
|
|
}
|
|
}
|
|
}
|
|
default {
|
|
// unsuccess:
|
|
success := 0
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
function equalStorage(bytes storage _preBytes, bytes memory _postBytes)
|
|
internal
|
|
view
|
|
returns (bool)
|
|
{
|
|
bool success = true;
|
|
|
|
assembly {
|
|
// we know _preBytes_offset is 0
|
|
let fslot := sload(_preBytes.slot)
|
|
// Decode the length of the stored array like in concatStorage().
|
|
let slength := div(
|
|
and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),
|
|
2
|
|
)
|
|
let mlength := mload(_postBytes)
|
|
|
|
// if lengths don't match the arrays are not equal
|
|
switch eq(slength, mlength)
|
|
case 1 {
|
|
// slength can contain both the length and contents of the array
|
|
// if length < 32 bytes so let's prepare for that
|
|
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
|
|
if iszero(iszero(slength)) {
|
|
switch lt(slength, 32)
|
|
case 1 {
|
|
// blank the last byte which is the length
|
|
fslot := mul(div(fslot, 0x100), 0x100)
|
|
|
|
if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
|
|
// unsuccess:
|
|
success := 0
|
|
}
|
|
}
|
|
default {
|
|
// cb is a circuit breaker in the for loop since there's
|
|
// no said feature for inline assembly loops
|
|
// cb = 1 - don't breaker
|
|
// cb = 0 - break
|
|
let cb := 1
|
|
|
|
// get the keccak hash to get the contents of the array
|
|
mstore(0x0, _preBytes.slot)
|
|
let sc := keccak256(0x0, 0x20)
|
|
|
|
let mc := add(_postBytes, 0x20)
|
|
let end := add(mc, mlength)
|
|
|
|
// the next line is the loop condition:
|
|
// while(uint256(mc < end) + cb == 2)
|
|
for {
|
|
|
|
} eq(add(lt(mc, end), cb), 2) {
|
|
sc := add(sc, 1)
|
|
mc := add(mc, 0x20)
|
|
} {
|
|
if iszero(eq(sload(sc), mload(mc))) {
|
|
// unsuccess:
|
|
success := 0
|
|
cb := 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
default {
|
|
// unsuccess:
|
|
success := 0
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
}
|
|
|
|
library Address {
|
|
/**
|
|
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
|
|
* revert reason using the provided one.
|
|
*/
|
|
function verifyCallResult(
|
|
bool success,
|
|
bytes memory returndata,
|
|
string memory errorMessage
|
|
) internal pure returns (bytes memory) {
|
|
if (success) {
|
|
return returndata;
|
|
} else {
|
|
// Look for revert reason and bubble it up if present
|
|
if (returndata.length > 0) {
|
|
// The easiest way to bubble the revert reason is using memory via assembly
|
|
/// @solidity memory-safe-assembly
|
|
assembly {
|
|
let returndata_size := mload(returndata)
|
|
revert(add(32, returndata), returndata_size)
|
|
}
|
|
} else {
|
|
revert(errorMessage);
|
|
}
|
|
}
|
|
}
|
|
}
|