-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathVault.sol
148 lines (132 loc) · 5.61 KB
/
Vault.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import {IVault} from "./interfaces/IVault.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {NFTReceiver} from "./utils/NFTReceiver.sol";
/// @title Vault
/// @author Fractional Art
/// @notice Proxy contract for storing fractionalized assets
contract Vault is IVault, NFTReceiver {
/// @notice Address of vault owner
address public owner;
/// @notice Merkle root hash of vault permissions
bytes32 public merkleRoot;
/// @notice Initializer value
uint256 public nonce;
/// @dev Minimum reserve of gas units
uint256 private constant MIN_GAS_RESERVE = 5_000;
/// @notice Mapping of function selector to plugin address
mapping(bytes4 => address) public methods;
/// @dev Initializes nonce and proxy owner
function init() external {
if (nonce != 0) revert Initialized(owner, msg.sender, nonce);
nonce = 1;
owner = msg.sender;
emit TransferOwnership(address(0), msg.sender);
}
/// @dev Callback for receiving Ether when the calldata is empty
receive() external payable {}
/// @dev Callback for handling plugin transactions
/// @param _data Transaction data
/// @return response Return data from executing plugin
// prettier-ignore
fallback(bytes calldata _data) external payable returns (bytes memory response) {
address plugin = methods[msg.sig];
(,response) = _execute(plugin, _data);
}
/// @notice Executes vault transactions through delegatecall
/// @param _target Target address
/// @param _data Transaction data
/// @param _proof Merkle proof of permission hash
/// @return success Result status of delegatecall
/// @return response Return data of delegatecall
function execute(
address _target,
bytes calldata _data,
bytes32[] calldata _proof
) external payable returns (bool success, bytes memory response) {
bytes4 selector;
assembly {
selector := calldataload(_data.offset)
}
// Generate leaf node by hashing module, target and function selector.
bytes32 leaf = keccak256(abi.encode(msg.sender, _target, selector));
// Check that the caller is either a module with permission to call or the owner.
if (!MerkleProof.verify(_proof, merkleRoot, leaf)) {
if (msg.sender != owner)
revert NotAuthorized(msg.sender, _target, selector);
}
(success, response) = _execute(_target, _data);
}
/// @notice Installs plugin by setting function selector to contract address
/// @param _selectors List of function selectors
/// @param _plugins Addresses of plugin contracts
function install(bytes4[] memory _selectors, address[] memory _plugins)
external
{
if (owner != msg.sender) revert NotOwner(owner, msg.sender);
uint256 length = _selectors.length;
for (uint256 i = 0; i < length; i++) {
methods[_selectors[i]] = _plugins[i];
}
emit InstallPlugin(_selectors, _plugins);
}
/// @notice Sets merkle root of vault permissions
/// @param _rootHash Hash of merkle root
function setMerkleRoot(bytes32 _rootHash) external {
if (owner != msg.sender) revert NotOwner(owner, msg.sender);
merkleRoot = _rootHash;
}
/// @notice Transfers ownership to given account
/// @param _newOwner Address of new owner
function transferOwnership(address _newOwner) external {
if (owner != msg.sender) revert NotOwner(owner, msg.sender);
owner = _newOwner;
emit TransferOwnership(msg.sender, _newOwner);
}
/// @notice Uninstalls plugin by setting function selector to zero address
/// @param _selectors List of function selectors
function uninstall(bytes4[] memory _selectors) external {
if (owner != msg.sender) revert NotOwner(owner, msg.sender);
uint256 length = _selectors.length;
for (uint256 i = 0; i < length; i++) {
methods[_selectors[i]] = address(0);
}
emit UninstallPlugin(_selectors);
}
/// @notice Executes plugin transactions through delegatecall
/// @param _target Target address
/// @param _data Transaction data
/// @return success Result status of delegatecall
/// @return response Return data of delegatecall
function _execute(address _target, bytes calldata _data)
internal
returns (bool success, bytes memory response)
{
// Check that the target is a valid contract
uint256 codeSize;
assembly {
codeSize := extcodesize(_target)
}
if (codeSize == 0) revert TargetInvalid(_target);
// Save the owner address in memory to ensure that it cannot be modified during the DELEGATECALL
address owner_ = owner;
// Reserve some gas to ensure that the function has enough to finish the execution
uint256 stipend = gasleft() - MIN_GAS_RESERVE;
// Delegate call to the target contract
(success, response) = _target.delegatecall{gas: stipend}(_data);
if (owner_ != owner) revert OwnerChanged(owner_, owner);
// Revert if execution was unsuccessful
if (!success) {
if (response.length == 0) revert ExecutionReverted();
_revertedWithReason(response);
}
}
/// @notice Reverts transaction with reason
function _revertedWithReason(bytes memory _response) internal pure {
assembly {
let returndata_size := mload(_response)
revert(add(32, _response), returndata_size)
}
}
}