-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathStakerOnBehalf.sol
295 lines (276 loc) · 12.1 KB
/
StakerOnBehalf.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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;
import {Staker} from "src/Staker.sol";
import {SignatureChecker} from "openzeppelin/utils/cryptography/SignatureChecker.sol";
import {EIP712} from "openzeppelin/utils/cryptography/EIP712.sol";
import {Nonces} from "openzeppelin/utils/Nonces.sol";
/// @title StakerOnBehalf
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract extension adds signature execution functionality to the Staker
/// base contract, allowing key operations to be executed via signatures rather than requiring the
/// owner or claimer to execute transactions directly. This includes staking, withdrawing,
/// altering delegatees and claimers, and claiming rewards. Each operation requires a unique
/// signature that is validated against the appropriate signer (owner or claimer) before
/// execution.
abstract contract StakerOnBehalf is Staker, EIP712, Nonces {
/// @notice Thrown when an onBehalf method is called with a deadline that has expired.
error StakerOnBehalf__ExpiredDeadline();
/// @notice Thrown if a caller supplies an invalid signature to a method that requires one.
error StakerOnBehalf__InvalidSignature();
/// @notice Type hash used when encoding data for `stakeOnBehalf` calls.
bytes32 public constant STAKE_TYPEHASH = keccak256(
"Stake(uint256 amount,address delegatee,address claimer,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `stakeMoreOnBehalf` calls.
bytes32 public constant STAKE_MORE_TYPEHASH = keccak256(
"StakeMore(uint256 depositId,uint256 amount,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `alterDelegateeOnBehalf` calls.
bytes32 public constant ALTER_DELEGATEE_TYPEHASH = keccak256(
"AlterDelegatee(uint256 depositId,address newDelegatee,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `alterClaimerOnBehalf` calls.
bytes32 public constant ALTER_CLAIMER_TYPEHASH = keccak256(
"AlterClaimer(uint256 depositId,address newClaimer,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `withdrawOnBehalf` calls.
bytes32 public constant WITHDRAW_TYPEHASH = keccak256(
"Withdraw(uint256 depositId,uint256 amount,address depositor,uint256 nonce,uint256 deadline)"
);
/// @notice Type hash used when encoding data for `claimRewardOnBehalf` calls.
bytes32 public constant CLAIM_REWARD_TYPEHASH =
keccak256("ClaimReward(uint256 depositId,uint256 nonce,uint256 deadline)");
/// @notice Returns the domain separator used in the encoding of the signatures for this contract.
/// @return The domain separator, as a bytes32 value, used for EIP-712 signatures.
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _domainSeparatorV4();
}
/// @notice Allows an address to increment their nonce and therefore invalidate any pending signed
/// actions.
function invalidateNonce() external virtual {
_useNonce(msg.sender);
}
/// @notice Stake tokens to a new deposit on behalf of a user, using a signature to validate the
/// user's intent. The caller must pre-approve the staking contract to spend at least the
/// would-be staked amount of the token.
/// @param _amount Quantity of the staking token to stake.
/// @param _delegatee Address to assign the governance voting weight of the staked tokens.
/// @param _claimer Address that will accrue rewards for this stake.
/// @param _depositor Address of the user on whose behalf this stake is being made.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @return _depositId Unique identifier for this deposit.
/// @dev Neither the delegatee nor the claimer may be the zero address.
function stakeOnBehalf(
uint256 _amount,
address _delegatee,
address _claimer,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual returns (DepositIdentifier _depositId) {
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
STAKE_TYPEHASH,
_amount,
_delegatee,
_claimer,
_depositor,
_useNonce(_depositor),
_deadline
)
)
),
_signature
);
_depositId = _stake(_depositor, _amount, _delegatee, _claimer);
}
/// @notice Add more staking tokens to an existing deposit on behalf of a user, using a signature
/// to validate the user's intent. A staker should call this method when they have an existing
/// deposit, and wish to stake more while retaining the same delegatee and claimer.
/// @param _depositId Unique identifier of the deposit to which stake will be added.
/// @param _amount Quantity of stake to be added.
/// @param _depositor Address of the user on whose behalf this stake is being made.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
function stakeMoreOnBehalf(
DepositIdentifier _depositId,
uint256 _amount,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
STAKE_MORE_TYPEHASH, _depositId, _amount, _depositor, _useNonce(_depositor), _deadline
)
)
),
_signature
);
_stakeMore(deposit, _depositId, _amount);
}
/// @notice For an existing deposit, change the address to which governance voting power is
/// assigned on behalf of a user, using a signature to validate the user's intent.
/// @param _depositId Unique identifier of the deposit which will have its delegatee altered.
/// @param _newDelegatee Address of the new governance delegate.
/// @param _depositor Address of the user on whose behalf this stake is being made.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @dev The new delegatee may not be the zero address.
function alterDelegateeOnBehalf(
DepositIdentifier _depositId,
address _newDelegatee,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
ALTER_DELEGATEE_TYPEHASH,
_depositId,
_newDelegatee,
_depositor,
_useNonce(_depositor),
_deadline
)
)
),
_signature
);
_alterDelegatee(deposit, _depositId, _newDelegatee);
}
/// @notice For an existing deposit, change the claimer account which has the right to
/// withdraw staking rewards accruing on behalf of a user, using a signature to validate the
/// user's intent.
/// @param _depositId Unique identifier of the deposit which will have its claimer altered.
/// @param _newClaimer Address of the new claimer.
/// @param _depositor Address of the user on whose behalf this stake is being made.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @dev The new claimer may not be the zero address.
function alterClaimerOnBehalf(
DepositIdentifier _depositId,
address _newClaimer,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
ALTER_CLAIMER_TYPEHASH,
_depositId,
_newClaimer,
_depositor,
_useNonce(_depositor),
_deadline
)
)
),
_signature
);
_alterClaimer(deposit, _depositId, _newClaimer);
}
/// @notice Withdraw staked tokens from an existing deposit on behalf of a user, using a
/// signature to validate the user's intent.
/// @param _depositId Unique identifier of the deposit from which stake will be withdrawn.
/// @param _amount Quantity of staked token to withdraw.
/// @param _depositor Address of the user on whose behalf this stake is being made.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the user authorizing this stake.
/// @dev Stake is withdrawn to the deposit owner's account.
function withdrawOnBehalf(
DepositIdentifier _depositId,
uint256 _amount,
address _depositor,
uint256 _deadline,
bytes memory _signature
) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, _depositor);
_revertIfPastDeadline(_deadline);
_revertIfSignatureIsNotValidNow(
_depositor,
_hashTypedDataV4(
keccak256(
abi.encode(
WITHDRAW_TYPEHASH, _depositId, _amount, _depositor, _useNonce(_depositor), _deadline
)
)
),
_signature
);
_withdraw(deposit, _depositId, _amount);
}
/// @notice Claim reward tokens earned by a given deposit, using a signature to validate the
/// caller's intent. The signer must be the claimer address of the deposit Tokens are sent to
/// the claimer.
/// @param _depositId The identifier for the deposit for which to claim rewards.
/// @param _deadline The timestamp after which the signature should expire.
/// @param _signature Signature of the claimer authorizing this reward claim.
/// @return Amount of reward tokens claimed, after the fee has been assessed.
function claimRewardOnBehalf(
DepositIdentifier _depositId,
uint256 _deadline,
bytes memory _signature
) external virtual returns (uint256) {
_revertIfPastDeadline(_deadline);
Deposit storage deposit = deposits[_depositId];
bytes32 _claimerHash = _hashTypedDataV4(
keccak256(abi.encode(CLAIM_REWARD_TYPEHASH, _depositId, nonces(deposit.claimer), _deadline))
);
bool _isValidClaimerClaim =
SignatureChecker.isValidSignatureNow(deposit.claimer, _claimerHash, _signature);
if (_isValidClaimerClaim) {
_useNonce(deposit.claimer);
return _claimReward(_depositId, deposit, deposit.claimer);
}
bytes32 _ownerHash = _hashTypedDataV4(
keccak256(abi.encode(CLAIM_REWARD_TYPEHASH, _depositId, _useNonce(deposit.owner), _deadline))
);
bool _isValidOwnerClaim =
SignatureChecker.isValidSignatureNow(deposit.owner, _ownerHash, _signature);
if (!_isValidOwnerClaim) revert StakerOnBehalf__InvalidSignature();
return _claimReward(_depositId, deposit, deposit.owner);
}
/// @notice Internal helper method which reverts if the provided deadline has passed.
/// @param _deadline The timestamp that represents when the operation should no longer be valid.
function _revertIfPastDeadline(uint256 _deadline) internal view virtual {
if (block.timestamp > _deadline) revert StakerOnBehalf__ExpiredDeadline();
}
/// @notice Internal helper method which reverts with Staker__InvalidSignature if the
/// signature is invalid.
/// @param _signer Address of the signer.
/// @param _hash Hash of the message.
/// @param _signature Signature to validate.
function _revertIfSignatureIsNotValidNow(address _signer, bytes32 _hash, bytes memory _signature)
internal
view
virtual
{
bool _isValid = SignatureChecker.isValidSignatureNow(_signer, _hash, _signature);
if (!_isValid) revert StakerOnBehalf__InvalidSignature();
}
}