Skip to content

Commit

Permalink
Merge pull request #177 from ERC725Alliance/overloadX/execute
Browse files Browse the repository at this point in the history
feat: overload execute function to batch execution
  • Loading branch information
frozeman authored Oct 31, 2022
2 parents 9d5008b + 463081c commit 9e46369
Show file tree
Hide file tree
Showing 12 changed files with 2,479 additions and 1,504 deletions.
68 changes: 53 additions & 15 deletions docs/ERC-725.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
eip: 725
title: General data key/value store and execution
title: General data key/value store and execution
description: An interface for a smart contract based account with attachable data key/value store
author: Fabian Vogelsteller (@frozeman), Tyler Yasaka (@tyleryasaka)
discussions-to: https://github.com/ethereum/EIPs/issues/725
Expand Down Expand Up @@ -39,22 +39,36 @@ And the event:

### ERC725X

ERC165 identifier: `0x44c028fe`
ERC165 identifier: `0x570ef073`

**Note:** The `execute()` function use function overloading, therefore it is better to reference them by the given function signature as follows:

```js
// web3.js example
myContract.methods['execute(uint256,address,uint256,bytes)'](OPERATION_CALL, target.address, 2WEI, "0x").send();

myContract.methods['execute(uint256[],address[],uint256[],bytes[])']([OPERATION_CALL, OPERATION_CREATE], [target.address, ZERO_ADDRESS], [2WEI, 0WEI], ["0x", CONTRACT_BYTECODE]).send()

// or
myContract.methods['0x44c028fe'](OPERATION_CALL, target.address, 2WEI, "0x").send();
```

#### execute

```solidity
function execute(uint256 operationType, address to, uint256 value, bytes memory data) public payable returns(bytes memory)
function execute(uint256 operationType, address target, uint256 value, bytes memory data) public payable returns(bytes memory)
```

Executes a call on any other smart contracts, transfers the blockchains native token, or deploys a new smart contract.
Function Selector: `0x44c028fe`

Executes a call on any other smart contracts, transfers the blockchain native token, or deploys a new smart contract.
MUST only be called by the current owner of the contract.
MUST revert when the execution fails.

_Parameters:_

- `operationType`: the operation type used to execute.
- `to`: the smart contract or address to call. `to` will be unused if a contract is created (operation types 1 and 2).
- `target`: the smart contract or address to call. `target` will be unused if a contract is created (operation types 1 and 2).
- `value`: the amount of native tokens to transfer (in Wei).
- `data`: the call data, or the creation bytecode of the contract to deploy.

Expand All @@ -65,15 +79,14 @@ _Parameters:_
- For operationType, `CREATE` the `data` field is the creation bytecode of the contract to deploy appended with the constructor argument(s) abi-encoded.

- For operationType, `CREATE2` the `data` field is the creation bytecode of the contract to deploy appended with:
1. the constructor argument(s) abi-encoded
2. a bytes32 salt.
1. the constructor argument(s) abi-encoded
2. a bytes32 salt.

```
data = <contract-creation-code> + <abi-encoded-constructor-arguments> + <bytes32-salt>
```

> Check [EIP-1014: Skinny CREATE2](https://eips.ethereum.org/EIPS/eip-1014) for more information.
_Returns:_ `bytes` , the returned data of the called function, or the address of the contract deployed (operation types 1 and 2).

Expand All @@ -91,6 +104,29 @@ Others may be added in the future.

> **Note:** the operation types `staticcall` (`3`) and `delegatecall` (`4`) do not allow to transfer value.
#### execute (Array)

```solidity
function execute(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes[] memory datas) public payable returns(bytes[] memory)
```

Function Selector: `0x13ced88d`

Executes a batch of calls on any other smart contracts, transfers the blockchain native token, or deploys a new smart contract.
MUST only be called by the current owner of the contract.
MUST revert when one execution at least fails.

_Parameters:_

- `operationsType`: the list of operations type used to execute.
- `targets`: the list of addresses to call. `targets` will be unused if a contract is created (operation types 1 and 2).
- `values`: the list of native token amounts to transfer (in Wei).
- `datas`: the list of call data, or the creation bytecode of the contract to deploy.

_Returns:_ `bytes[]` , array list of returned data of the called function, or the address(es) of the contract deployed (operation types 1 and 2).

**Triggers Event:** [ContractCreated](#contractcreated), [Executed](#executed) on each call iteration

### Events

#### Executed
Expand Down Expand Up @@ -127,7 +163,7 @@ myContract.methods['0x14a6e293']([dataKeys, ...], [dataValues, ...]).send()
function getData(bytes32 dataKey) public view returns(bytes memory)
```

Function Signature: `0x54f6127f`
Function Selector: `0x54f6127f`

Gets the data set for the given data key.

Expand All @@ -143,7 +179,7 @@ _Returns:_ `bytes` , The data for the requested key.
function getData(bytes32[] memory dataKeys) public view returns(bytes[] memory)
```

Function Signature: `0x4e3e6e9c`
Function Selector: `0x4e3e6e9c`

Gets array of data at multiple given key.

Expand All @@ -159,7 +195,7 @@ _Returns:_ `bytes[]` , array of data values for the requested data keys.
function setData(bytes32 dataKey, bytes memory dataValue) public
```

Function Signature: `0x7f23690c`
Function Selector: `0x7f23690c`

Sets data as bytes in the storage for a single key. MUST only be called by the current owner of the contract.

Expand All @@ -176,7 +212,7 @@ _Parameters:_
function setData(bytes32[] memory dataKeys, bytes[] memory dataValues) public
```

Function Signature: `0x14a6e293`
Function Selector: `0x14a6e293`

Sets array of data at multiple keys. MUST only be called by the current owner of the contract.

Expand Down Expand Up @@ -212,7 +248,7 @@ The generic way of storing data key with values was chosen to allow upgradablity

## Backwards Compatibility

All contracts since ERC725v2 from 2018/19 should be compatible to the current version of the standard. Mainly interface ID and Event parameters have changed, while `getData(bytes32[])` and `setData(bytes32[], bytes[])` was added as an efficient way to set/get multiple keys at once.
All contracts since ERC725v2 from 2018/19 should be compatible to the current version of the standard. Mainly interface ID and Event parameters have changed, while `getData(bytes32[])` and `setData(bytes32[], bytes[])` was added as an efficient way to set/get multiple keys at once. The same applies for execution, as `execute(..[])` was added as an efficient way to batch calls at once.

## Reference Implementation

Expand All @@ -230,12 +266,14 @@ When using the operation type `4` for `delegatecall`, it is important to conside
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.5.0 <0.7.0;
// ERC165 identifier: `0x44c028fe`
// ERC165 identifier: `0x570ef073`
interface IERC725X /* is ERC165, ERC173 */ {
event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value);
event Executed(uint256 indexed operationType, address indexed to, uint256 indexed value, bytes4 data);
function execute(uint256 operationType, address to, uint256 value, bytes memory data) external payable returns(bytes memory);
function execute(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes memory datas) external payable returns(bytes[] memory);
}
// ERC165 identifier: `0x714df77c`
Expand Down
10 changes: 9 additions & 1 deletion implementations/constants.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const INTERFACE_ID = {
ERC165: '0x01ffc9a7',
ERC725X: '0x44c028fe',
ERC725X: '0x570ef073',
ERC725Y: '0x714df77c',
};

Expand All @@ -12,7 +12,15 @@ const OPERATION_TYPE = {
DELEGATECALL: 4,
};

const FUNCTIONS_SELECTOR = {
EXECUTE: '0x44c028fe',
EXECUTE_ARRAY: '0x13ced88d',
SETDATA: '0x7f23690c',
SETDATA_ARRAY: '0x14a6e293',
};

module.exports = {
INTERFACE_ID,
OPERATION_TYPE,
FUNCTIONS_SELECTOR,
};
107 changes: 72 additions & 35 deletions implementations/contracts/ERC725XCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {OwnableUnset} from "./custom/OwnableUnset.sol";

// constants
import {
_INTERFACEID_ERC725X,
_INTERFACEID_ERC725X,
OPERATION_0_CALL,
OPERATION_1_CREATE,
OPERATION_2_CREATE2,
Expand All @@ -39,14 +39,37 @@ abstract contract ERC725XCore is OwnableUnset, ERC165, IERC725X {
*/
function execute(
uint256 operationType,
address to,
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, to, value, data);
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 = _uncheckedIncrementERC725X(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]);
}
}

/**
Expand All @@ -68,31 +91,31 @@ abstract contract ERC725XCore is OwnableUnset, ERC165, IERC725X {
*/
function _execute(
uint256 operationType,
address to,
address target,
uint256 value,
bytes memory data
) internal virtual returns (bytes memory) {
// CALL
if (operationType == OPERATION_0_CALL) {
return _executeCall(to, value, data);
return _executeCall(target, value, data);
}

// Deploy with CREATE
if (operationType == uint256(OPERATION_1_CREATE)) {
if (to != address(0)) revert ERC725X_CreateOperationsRequireEmptyRecipientAddress();
if (target != address(0)) revert ERC725X_CreateOperationsRequireEmptyRecipientAddress();
return _deployCreate(value, data);
}

// Deploy with CREATE2
if (operationType == uint256(OPERATION_2_CREATE2)) {
if (to != address(0)) revert ERC725X_CreateOperationsRequireEmptyRecipientAddress();
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(to, data);
return _executeStaticCall(target, data);
}

// DELEGATECALL
Expand All @@ -109,62 +132,64 @@ abstract contract ERC725XCore is OwnableUnset, ERC165, IERC725X {
//
if (operationType == uint256(OPERATION_4_DELEGATECALL)) {
if (value != 0) revert ERC725X_MsgValueDisallowedInDelegateCall();
return _executeDelegateCall(to, data);
return _executeDelegateCall(target, data);
}

revert ERC725X_UnknownOperationType(operationType);
}

/**
* @dev perform low-level call (operation type = 0)
* @param to The address on which call is executed
* @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 to,
address target,
uint256 value,
bytes memory data
) internal virtual returns (bytes memory result) {
emit Executed(OPERATION_0_CALL, to, value, bytes4(data));
emit Executed(OPERATION_0_CALL, target, value, bytes4(data));

// solhint-disable avoid-low-level-calls
(bool success, bytes memory returnData) = to.call{value: value}(data);
(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 to The address on which staticcall is executed
* @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 to,
bytes memory data
) internal virtual returns (bytes memory result) {
emit Executed(OPERATION_3_STATICCALL, to, 0, bytes4(data));
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) = to.staticcall(data);
(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 to The address on which delegatecall is executed
* @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 to,
bytes memory data
) internal virtual returns (bytes memory result) {
emit Executed(OPERATION_4_DELEGATECALL, to, 0, bytes4(data));
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) = to.delegatecall(data);
(bool success, bytes memory returnData) = target.delegatecall(data);
result = Address.verifyCallResult(success, returnData, "ERC725X: Unknown Error");
}

Expand All @@ -174,10 +199,11 @@ abstract contract ERC725XCore is OwnableUnset, ERC165, IERC725X {
* @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) {
function _deployCreate(uint256 value, bytes memory creationCode)
internal
virtual
returns (bytes memory newContract)
{
if (creationCode.length == 0) {
revert ERC725X_NoContractBytecodeProvided();
}
Expand All @@ -202,10 +228,11 @@ abstract contract ERC725XCore is OwnableUnset, ERC165, IERC725X {
* @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) {
function _deployCreate2(uint256 value, bytes memory creationCode)
internal
virtual
returns (bytes memory newContract)
{
if (creationCode.length == 0) {
revert ERC725X_NoContractBytecodeProvided();
}
Expand All @@ -217,4 +244,14 @@ abstract contract ERC725XCore is OwnableUnset, ERC165, IERC725X {
newContract = abi.encodePacked(contractAddress);
emit ContractCreated(OPERATION_2_CREATE2, contractAddress, value);
}

/**
* @dev Will return unchecked incremented uint256
* can be used to save gas when iterating over loops
*/
function _uncheckedIncrementERC725X(uint256 i) internal pure returns (uint256) {
unchecked {
return i + 1;
}
}
}
Loading

0 comments on commit 9e46369

Please sign in to comment.