forked from kamescg/delegatable-sol
-
Notifications
You must be signed in to change notification settings - Fork 15
/
DelegatableCore.sol
154 lines (134 loc) · 5.54 KB
/
DelegatableCore.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {EIP712Decoder, EIP712DOMAIN_TYPEHASH} from "./TypesAndDecoders.sol";
import {Delegation, Invocation, Invocations, SignedInvocation, SignedDelegation, Transaction, ReplayProtection, CaveatEnforcer} from "./CaveatEnforcer.sol";
abstract contract DelegatableCore is EIP712Decoder {
/// @notice Account delegation nonce manager
mapping(address => mapping(uint256 => uint256)) internal multiNonce;
function getNonce(address intendedSender, uint256 queue)
external
view
returns (uint256)
{
return multiNonce[intendedSender][queue];
}
function verifyDelegationSignature(SignedDelegation memory signedDelegation)
public
view
virtual
returns (address);
function _enforceReplayProtection(
address intendedSender,
ReplayProtection memory protection
) internal {
uint256 queue = protection.queue;
uint256 nonce = protection.nonce;
require(
nonce == (multiNonce[intendedSender][queue] + 1),
"DelegatableCore:nonce2-out-of-order"
);
multiNonce[intendedSender][queue] = nonce;
}
function _execute(
address to,
bytes memory data,
uint256 gasLimit,
address sender
) internal returns (bool success) {
bytes memory full = abi.encodePacked(data, sender);
assembly {
success := call(gasLimit, to, 0, add(full, 0x20), mload(full), 0, 0)
}
}
function _invoke(Invocation[] calldata batch, address sender)
internal
returns (bool success)
{
for (uint256 x = 0; x < batch.length; x++) {
Invocation memory invocation = batch[x];
address intendedSender;
address canGrant;
// If there are no delegations, this invocation comes from the signer
if (invocation.authority.length == 0) {
intendedSender = sender;
canGrant = intendedSender;
}
bytes32 authHash = 0x0;
for (uint256 d = 0; d < invocation.authority.length; d++) {
SignedDelegation memory signedDelegation = invocation.authority[
d
];
address delegationSigner = verifyDelegationSignature(
signedDelegation
);
// Implied sending account is the signer of the first delegation
if (d == 0) {
intendedSender = delegationSigner;
canGrant = intendedSender;
}
require(
delegationSigner == canGrant,
"DelegatableCore:invalid-delegation-signer"
);
Delegation memory delegation = signedDelegation.delegation;
require(
delegation.authority == authHash,
"DelegatableCore:invalid-authority-delegation-link"
);
// TODO: maybe delegations should have replay protection, at least a nonce (non order dependent),
// otherwise once it's revoked, you can't give the exact same permission again.
bytes32 delegationHash = GET_SIGNEDDELEGATION_PACKETHASH(
signedDelegation
);
// Each delegation can include any number of caveats.
// A caveat is any condition that may reject a proposed transaction.
// The caveats specify an external contract that is passed the proposed tx,
// As well as some extra terms that are used to parameterize the enforcer.
for (uint16 y = 0; y < delegation.caveats.length; y++) {
CaveatEnforcer enforcer = CaveatEnforcer(
delegation.caveats[y].enforcer
);
bool caveatSuccess = enforcer.enforceCaveat(
delegation.caveats[y].terms,
invocation.transaction,
delegationHash
);
require(caveatSuccess, "DelegatableCore:caveat-rejected");
}
// Store the hash of this delegation in `authHash`
// That way the next delegation can be verified against it.
authHash = delegationHash;
canGrant = delegation.delegate;
}
// Here we perform the requested invocation.
Transaction memory transaction = invocation.transaction;
require(
transaction.to == address(this),
"DelegatableCore:invalid-invocation-target"
);
// TODO(@kames): Can we bubble up the error message from the enforcer? Why not? Optimizations?
success = _execute(
transaction.to,
transaction.data,
transaction.gasLimit,
intendedSender
);
require(success, "DelegatableCore::execution-failed");
}
}
function _msgSender() internal view virtual returns (address sender) {
if (msg.sender == address(this)) {
bytes memory array = msg.data;
uint256 index = msg.data.length;
assembly {
sender := and(
mload(add(array, index)),
0xffffffffffffffffffffffffffffffffffffffff
)
}
} else {
sender = msg.sender;
}
return sender;
}
}