generated from scaffold-eth/scaffold-eth
-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathDelegatable.sol
170 lines (142 loc) · 6.49 KB
/
Delegatable.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
pragma solidity ^0.8.13;
// SPDX-License-Identifier: MIT
import "./TypesAndDecoders.sol";
import "./caveat-enforcers/CaveatEnforcer.sol";
abstract contract Delegatable is EIP712Decoder {
bytes32 public immutable domainHash;
constructor (string memory contractName, string memory version) {
domainHash = getEIP712DomainHash(contractName,version,block.chainid,address(this));
}
// Allows external signers to submit batches of signed invocations for processing.
function invoke (SignedInvocation[] calldata signedInvocations) public returns (bool success) {
for (uint i = 0; i < signedInvocations.length; i++) {
SignedInvocation calldata signedInvocation = signedInvocations[i];
address invocationSigner = verifyInvocationSignature(signedInvocation);
enforceReplayProtection(invocationSigner, signedInvocations[i].invocations.replayProtection);
_invoke(signedInvocation.invocations.batch, invocationSigner);
}
}
// Allows external contracts to submit batches of invocations for processing.
function contractInvoke (Invocation[] calldata batch) public returns (bool) {
return _invoke(batch, msg.sender);
}
function _invoke (Invocation[] calldata batch, address sender) private returns (bool success) {
for (uint 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 (uint 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, "Delegation signer does not match required signer");
Delegation memory delegation = signedDelegation.delegation;
require(delegation.authority == authHash, "Delegation authority does not match previous delegation");
// 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, "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), "Invocation target does not match");
success = execute(
transaction.to,
transaction.data,
transaction.gasLimit,
intendedSender
);
require(success, "Delegator execution failed");
}
}
mapping(address => mapping(uint => uint)) public multiNonce;
function enforceReplayProtection (address intendedSender, ReplayProtection memory protection) private {
uint queue = protection.queue;
uint nonce = protection.nonce;
require(nonce == (multiNonce[intendedSender][queue]+1), "One-at-a-time order enforced. Nonce2 is too small");
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 verifyInvocationSignature (SignedInvocation memory signedInvocation) public view returns (address) {
bytes32 sigHash = getInvocationsTypedDataHash(signedInvocation.invocations);
address recoveredSignatureSigner = recover(sigHash, signedInvocation.signature);
return recoveredSignatureSigner;
}
function verifyDelegationSignature (SignedDelegation memory signedDelegation) public view returns (address) {
Delegation memory delegation = signedDelegation.delegation;
bytes32 sigHash = getDelegationTypedDataHash(delegation);
address recoveredSignatureSigner = recover(sigHash, signedDelegation.signature);
return recoveredSignatureSigner;
}
function getDelegationTypedDataHash(Delegation memory delegation) public view returns (bytes32) {
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
domainHash,
GET_DELEGATION_PACKETHASH(delegation)
));
return digest;
}
function getInvocationsTypedDataHash (Invocations memory invocations) public view returns (bytes32) {
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
domainHash,
GET_INVOCATIONS_PACKETHASH(invocations)
));
return digest;
}
function getEIP712DomainHash(string memory contractName, string memory version, uint256 chainId, address verifyingContract) public pure returns (bytes32) {
bytes memory encoded = abi.encode(
EIP712DOMAIN_TYPEHASH,
keccak256(bytes(contractName)),
keccak256(bytes(version)),
chainId,
verifyingContract
);
return keccak256(encoded);
}
function _msgSender () internal view virtual returns (address sender) {
if(msg.sender == address(this)) {
bytes memory array = msg.data;
uint256 index = msg.data.length;
assembly {
// Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
sender := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff)
}
} else {
sender = msg.sender;
}
return sender;
}
}