-
Notifications
You must be signed in to change notification settings - Fork 1
/
Identity.sol
164 lines (146 loc) · 6.41 KB
/
Identity.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
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.7;
import "./libs/SignatureValidatorV2.sol";
contract Identity {
mapping (address => bytes32) public privileges;
// The next allowed nonce
uint public nonce = 0;
// Events
event LogPrivilegeChanged(address indexed addr, bytes32 priv);
event LogErr(address indexed to, uint value, bytes data, bytes returnData); // only used in tryCatch
// Transaction structure
// we handle replay protection separately by requiring (address(this), chainID, nonce) as part of the sig
struct Transaction {
address to;
uint value;
bytes data;
}
constructor(address[] memory addrs) {
uint len = addrs.length;
for (uint i=0; i<len; i++) {
// @TODO should we allow setting to any arb value here?
privileges[addrs[i]] = bytes32(uint(1));
emit LogPrivilegeChanged(addrs[i], bytes32(uint(1)));
}
}
// This contract can accept ETH without calldata
receive() external payable {}
// This contract can accept ETH with calldata
// However, to support EIP 721 and EIP 1155, we need to respond to those methods with their own method signature
fallback() external payable {
bytes4 method = msg.sig;
if (
method == 0x150b7a02 // bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))
|| method == 0xf23a6e61 // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))
|| method == 0xbc197c81 // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))
) {
// Copy back the method
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, 0x04)
return (0, 0x20)
}
}
}
function setAddrPrivilege(address addr, bytes32 priv)
external
{
require(msg.sender == address(this), 'ONLY_IDENTITY_CAN_CALL');
// Anti-bricking measure: if the privileges slot is used for special data (not 0x01),
// don't allow to set it to true
if (privileges[addr] != bytes32(0) && privileges[addr] != bytes32(uint(1)))
require(priv != bytes32(uint(1)), 'UNSETTING_SPECIAL_DATA');
privileges[addr] = priv;
emit LogPrivilegeChanged(addr, priv);
}
function tipMiner(uint amount)
external
{
require(msg.sender == address(this), 'ONLY_IDENTITY_CAN_CALL');
// See https://docs.flashbots.net/flashbots-auction/searchers/advanced/coinbase-payment/#managing-payments-to-coinbaseaddress-when-it-is-a-contract
// generally this contract is reentrancy proof cause of the nonce
executeCall(block.coinbase, amount, new bytes(0));
}
function tryCatch(address to, uint value, bytes calldata data)
external
{
require(msg.sender == address(this), 'ONLY_IDENTITY_CAN_CALL');
(bool success, bytes memory returnData) = to.call{value: value, gas: gasleft()}(data);
if (!success) emit LogErr(to, value, data, returnData);
}
// WARNING: if the signature of this is changed, we have to change IdentityFactory
function execute(Transaction[] calldata txns, bytes calldata signature)
external
{
require(txns.length > 0, 'MUST_PASS_TX');
// If we use the naive abi.encode(txn) and have a field of type `bytes`,
// there is a discrepancy between ethereumjs-abi and solidity
// @TODO check if this is resolved
uint currentNonce = nonce;
// NOTE: abi.encode is safer than abi.encodePacked in terms of collision safety
bytes32 hash = keccak256(abi.encode(address(this), block.chainid, currentNonce, txns));
// We have to increment before execution cause it protects from reentrancies
nonce = currentNonce + 1;
address signer = SignatureValidator.recoverAddrImpl(hash, signature, true);
require(privileges[signer] != bytes32(0), 'INSUFFICIENT_PRIVILEGE');
uint len = txns.length;
for (uint i=0; i<len; i++) {
Transaction memory txn = txns[i];
executeCall(txn.to, txn.value, txn.data);
}
// The actual anti-bricking mechanism - do not allow a signer to drop their own priviledges
require(privileges[signer] != bytes32(0), 'PRIVILEGE_NOT_DOWNGRADED');
}
// no need for nonce management here cause we're not dealing with sigs
function executeBySender(Transaction[] calldata txns) external {
require(txns.length > 0, 'MUST_PASS_TX');
require(privileges[msg.sender] != bytes32(0), 'INSUFFICIENT_PRIVILEGE');
uint len = txns.length;
for (uint i=0; i<len; i++) {
Transaction memory txn = txns[i];
executeCall(txn.to, txn.value, txn.data);
}
// again, anti-bricking
require(privileges[msg.sender] != bytes32(0), 'PRIVILEGE_NOT_DOWNGRADED');
}
// we shouldn't use address.call(), cause: https://github.com/ethereum/solidity/issues/2884
// copied from https://github.com/uport-project/uport-identity/blob/develop/contracts/Proxy.sol
// there's also
// https://github.com/gnosis/MultiSigWallet/commit/e1b25e8632ca28e9e9e09c81bd20bf33fdb405ce
// https://github.com/austintgriffith/bouncer-proxy/blob/master/BouncerProxy/BouncerProxy.sol
// https://github.com/gnosis/safe-contracts/blob/7e2eeb3328bb2ae85c36bc11ea6afc14baeb663c/contracts/base/Executor.sol
function executeCall(address to, uint256 value, bytes memory data)
internal
{
assembly {
let result := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0)
switch result case 0 {
let size := returndatasize()
let ptr := mload(0x40)
returndatacopy(ptr, 0, size)
revert(ptr, size)
}
default {}
}
// A single call consumes around 477 more gas with the pure solidity version, for whatever reason
//(bool success, bytes memory returnData) = to.call{value: value, gas: gasleft()}(data);
//if (!success) revert(string(data));
}
// EIP 1271 implementation
// see https://eips.ethereum.org/EIPS/eip-1271
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4) {
if (privileges[SignatureValidator.recoverAddr(hash, signature)] != bytes32(0)) {
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
return 0x1626ba7e;
} else {
return 0xffffffff;
}
}
// EIP 1155 implementation
// we pretty much only need to signal that we support the interface for 165, but for 1155 we also need the fallback function
function supportsInterface(bytes4 interfaceID) external pure returns (bool) {
return
interfaceID == 0x01ffc9a7 || // ERC-165 support (i.e. `bytes4(keccak256('supportsInterface(bytes4)'))`).
interfaceID == 0x4e2312e0; // ERC-1155 `ERC1155TokenReceiver` support (i.e. `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) ^ bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`).
}
}