-
Notifications
You must be signed in to change notification settings - Fork 61
/
ERC725XCore.sol
187 lines (152 loc) · 6.06 KB
/
ERC725XCore.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
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
// interfaces
import {IERC725X} from "./interfaces/IERC725X.sol";
// libraries
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol";
import {ErrorHandlerLib} from "./utils/ErrorHandlerLib.sol";
// modules
import {OwnableUnset} from "./utils/OwnableUnset.sol";
// constants
// constants
// prettier-ignore
import {
OPERATION_CALL,
OPERATION_DELEGATECALL,
OPERATION_STATICCALL,
OPERATION_CREATE,
OPERATION_CREATE2
} from "./constants.sol";
/**
* @title Core implementation of ERC725 X executor
* @author Fabian Vogelsteller <fabian@lukso.network>
* @dev Implementation of a contract module which provides the ability to call arbitrary functions at any other smart contract and itself,
* including using `delegatecall`, `staticcall` as well creating contracts using `create` and `create2`
* This is the basis for a smart contract based account system, but could also be used as a proxy account system
*/
abstract contract ERC725XCore is IERC725X, OwnableUnset {
/* Public functions */
/**
* @inheritdoc IERC725X
*/
function execute(
uint256 _operation,
address _to,
uint256 _value,
bytes calldata _data
) public payable virtual override onlyOwner returns (bytes memory result) {
require(address(this).balance >= _value, "ERC725X: insufficient balance for call");
uint256 txGas = gasleft();
// CALL
if (_operation == OPERATION_CALL) {
result = executeCall(_to, _value, _data, txGas);
emit Executed(_operation, _to, _value, _data);
// STATICCALL
} else if (_operation == OPERATION_STATICCALL) {
result = executeStaticCall(_to, _data, txGas);
emit Executed(_operation, _to, _value, _data);
// DELEGATECALL
} else if (_operation == OPERATION_DELEGATECALL) {
address currentOwner = owner();
result = executeDelegateCall(_to, _data, txGas);
emit Executed(_operation, _to, _value, _data);
require(owner() == currentOwner, "Delegate call is not allowed to modify the owner!");
// CREATE
} else if (_operation == OPERATION_CREATE) {
address contractAddress = performCreate(_value, _data);
result = abi.encodePacked(contractAddress);
emit ContractCreated(_operation, contractAddress, _value);
// CREATE2
} else if (_operation == OPERATION_CREATE2) {
bytes32 salt = BytesLib.toBytes32(_data, _data.length - 32);
bytes memory data = BytesLib.slice(_data, 0, _data.length - 32);
address contractAddress = Create2.deploy(_value, salt, data);
result = abi.encodePacked(contractAddress);
emit ContractCreated(_operation, contractAddress, _value);
} else {
revert("Wrong operation type");
}
}
/* Internal functions */
/**
* @dev perform staticcall using operation 3
* Taken from GnosisSafe: https://github.com/gnosis/safe-contracts/blob/main/contracts/base/Executor.sol
*
* @param to The address on which staticcall is executed
* @param value The value to be sent with the call
* @param data The data to be sent with the call
* @param txGas The amount of gas for performing staticcall
* @return The data from the call
*/
function executeCall(
address to,
uint256 value,
bytes memory data,
uint256 txGas
) internal returns (bytes memory) {
// solhint-disable avoid-low-level-calls
(bool success, bytes memory result) = to.call{gas: txGas, value: value}(data);
if (!success) {
ErrorHandlerLib.revertWithParsedError(result);
}
return result;
}
/**
* @dev perform staticcall using operation 3
* @param to The address on which staticcall is executed
* @param data The data to be sent with the call
* @param txGas The amount of gas for performing staticcall
* @return The data from the call
*/
function executeStaticCall(
address to,
bytes memory data,
uint256 txGas
) internal view returns (bytes memory) {
(bool success, bytes memory result) = to.staticcall{gas: txGas}(data);
if (!success) {
ErrorHandlerLib.revertWithParsedError(result);
}
return result;
}
/**
* @dev perform delegatecall using operation 4
* Taken from GnosisSafe: https://github.com/gnosis/safe-contracts/blob/main/contracts/base/Executor.sol
*
* @param to The address on which delegatecall is executed
* @param data The data to be sent with the call
* @param txGas The amount of gas for performing delegatecall
* @return The data from the call
*/
function executeDelegateCall(
address to,
bytes memory data,
uint256 txGas
) internal returns (bytes memory) {
// solhint-disable avoid-low-level-calls
(bool success, bytes memory result) = to.delegatecall{gas: txGas}(data);
if (!success) {
ErrorHandlerLib.revertWithParsedError(result);
}
return result;
}
/**
* @dev perform contract creation using operation 1
* Taken from GnosisSafe: https://github.com/gnosis/safe-contracts/blob/main/contracts/libraries/CreateCall.sol
*
* @param value The value to be sent to the contract created
* @param deploymentData The contract bytecode to deploy
* @return newContract The address of the contract created
*/
function performCreate(uint256 value, bytes memory deploymentData)
internal
returns (address newContract)
{
require(deploymentData.length != 0, "no contract bytecode provided");
assembly {
newContract := create(value, add(deploymentData, 0x20), mload(deploymentData))
}
require(newContract != address(0), "Could not deploy contract");
}
}