@pvium/p2id-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -0
- package/contracts/P2IDVault.sol +736 -0
- package/contracts/PviumIdentity.sol +268 -0
- package/contracts/PviumP2IDPolicy.sol +78 -0
- package/contracts/PviumP2IdVaultFactory.sol +327 -0
- package/contracts/PviumVerifier.sol +165 -0
- package/contracts/PviumZKVerifier.sol +2465 -0
- package/contracts/interfaces/IP2IDPolicy.sol +39 -0
- package/contracts/interfaces/IP2IDVault.sol +112 -0
- package/contracts/interfaces/IP2IDVerifier.sol +44 -0
- package/contracts/interfaces/IP2IdVaultFactory.sol +49 -0
- package/contracts/interfaces/IPviumIdentity.sol +28 -0
- package/contracts/lib/P2IDHash.sol +63 -0
- package/dist/cjs/identity.js +68 -0
- package/dist/cjs/identityNames.js +49 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/p2id.js +91 -0
- package/dist/cjs/p2idConstants.js +16 -0
- package/dist/cjs/package.json +3 -0
- package/dist/esm/identity.d.ts +38 -0
- package/dist/esm/identity.js +61 -0
- package/dist/esm/identityNames.d.ts +43 -0
- package/dist/esm/identityNames.js +44 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/p2id.d.ts +54 -0
- package/dist/esm/p2id.js +82 -0
- package/dist/esm/p2idConstants.d.ts +30 -0
- package/dist/esm/p2idConstants.js +13 -0
- package/dist/esm/package.json +3 -0
- package/dist/identity.d.ts +38 -0
- package/dist/identity.js +61 -0
- package/dist/identityNames.d.ts +43 -0
- package/dist/identityNames.js +44 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/p2id.d.ts +54 -0
- package/dist/p2id.js +82 -0
- package/dist/p2idConstants.d.ts +38 -0
- package/dist/p2idConstants.js +21 -0
- package/package.json +44 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {IP2IDVault} from "./interfaces/IP2IDVault.sol";
|
|
5
|
+
import {IP2IDVerifier} from "./interfaces/IP2IDVerifier.sol";
|
|
6
|
+
import {IP2IDPolicy} from "./interfaces/IP2IDPolicy.sol";
|
|
7
|
+
import {IP2IdVaultFactory} from "./interfaces/IP2IdVaultFactory.sol";
|
|
8
|
+
|
|
9
|
+
/// @title P2IDVault
|
|
10
|
+
/// @notice A namespace-bound vault that holds ERC-20 funds until the identity owner claims them.
|
|
11
|
+
/// @dev Holds ERC-20 tokens and the chain's native coin (BNB, ETH), the latter identified by
|
|
12
|
+
/// token == NATIVE (address(0)) everywhere: in deposits, buckets, sweeps, fees and views.
|
|
13
|
+
///
|
|
14
|
+
/// **A bare transfer to this contract (ERC-20 transfer, or plain native send) is irrevocable:
|
|
15
|
+
/// it records no funder, has no refund path, and is claimable only through the factory's
|
|
16
|
+
/// default verifier.** Use fund() when refund rights, a funding constraint, or a specific
|
|
17
|
+
/// verifier are required.
|
|
18
|
+
///
|
|
19
|
+
/// This contract holds mechanics only; everything expected to evolve is decided by the
|
|
20
|
+
/// factory's policy (IP2IDPolicy), consulted on every call, so it can change without
|
|
21
|
+
/// changing this bytecode and therefore without moving any P2ID address. The limits a
|
|
22
|
+
/// policy can never cross are enforced here:
|
|
23
|
+
/// - fees are capped at MAX_FEE_BPS, fixed per deposit when it is made (a failing quote
|
|
24
|
+
/// means no fee), and accrue in the vault per (verifier, token); a payout never calls
|
|
25
|
+
/// the policy about fees, so fee handling can never block a claim. withdrawFees hands
|
|
26
|
+
/// accrued fees to the current policy, which pulls exactly the approved amount and
|
|
27
|
+
/// distributes it;
|
|
28
|
+
/// - refunds never pay a fee and never consult the policy;
|
|
29
|
+
/// - the policy can gate which verifiers are usable (freezing claims under a disallowed
|
|
30
|
+
/// one), but cannot redirect a payout: every claim still pays the wallet the verifier
|
|
31
|
+
/// resolved for this vault's identity. Claims through the factory's current default
|
|
32
|
+
/// verifier can never be frozen: direct transfers have no refund path, so the only thing
|
|
33
|
+
/// that may change who receives them is a default-verifier change with 14 days' notice.
|
|
34
|
+
/// (The policy still gates *funding* under the default, so a revoked default takes no
|
|
35
|
+
/// new deposits.)
|
|
36
|
+
///
|
|
37
|
+
/// Verifiers. Every deposit names the IP2IDVerifier whose proofs can release it, chosen by
|
|
38
|
+
/// the payer among those the policy allows (fund() takes the factory default). Every claim
|
|
39
|
+
/// path hands the verifier this vault's `saltCommitment` plus a proof and gets back the
|
|
40
|
+
/// wallet to pay; the verifier reverts unless the proof is for exactly that identity.
|
|
41
|
+
/// A constrained deposit is refused under a verifier that cannot satisfy constraints.
|
|
42
|
+
///
|
|
43
|
+
/// Accounting is per bucket, where a bucket is (verifier, constraint, token) and the
|
|
44
|
+
/// default bucket of a verifier is constraint == bytes32(0). Each bucket keeps its own
|
|
45
|
+
/// deposit-id list and cursor, so sweeps are bounded per bucket, can be paged
|
|
46
|
+
/// (`depositCountLimit` = number of deposit records visited from the cursor, consumed or
|
|
47
|
+
/// not; 0 = all) or aimed at specific ids, and spam in other tokens or buckets never blocks
|
|
48
|
+
/// a sweep.
|
|
49
|
+
///
|
|
50
|
+
/// Proof freshness ratchet, per verifier: every proof presented must be at least as fresh
|
|
51
|
+
/// as the newest one this vault has seen under that verifier, and a newer proof becomes the
|
|
52
|
+
/// owner proof, so presenting a proof for a new wallet retires every older proof. Funds
|
|
53
|
+
/// already inside before that reset are not protected: reset immediately after a wallet
|
|
54
|
+
/// compromise.
|
|
55
|
+
///
|
|
56
|
+
/// Direct transfers follow the factory's default verifier, which governance can change after 14
|
|
57
|
+
/// days' public notice. So that a change cannot bring back a wallet the owner has already moved away from,
|
|
58
|
+
/// direct transfers have their own freshness floor, `untrackedProofIat`: the newest proof
|
|
59
|
+
/// presented through whichever verifier was the default at the time. It carries across a
|
|
60
|
+
/// default change, and direct transfers are paid only to a default-verifier owner whose proof
|
|
61
|
+
/// is at least that fresh. Only the default verifier (chosen by governance, under the
|
|
62
|
+
/// timelock) can raise it, so a verifier payers merely opted into cannot affect it.
|
|
63
|
+
///
|
|
64
|
+
/// Invariant kept by every path: token balance >= trackedTotal[token] + feesOwedTotal[token].
|
|
65
|
+
contract P2IDVault is IP2IDVault {
|
|
66
|
+
/// @notice Hard cap on the fee any policy can charge on a payout: 1%. Part of this bytecode.
|
|
67
|
+
uint16 public constant MAX_FEE_BPS = 100;
|
|
68
|
+
/// @notice The token address that stands for the chain's native coin (BNB on BNB Chain).
|
|
69
|
+
address public constant NATIVE = address(0);
|
|
70
|
+
uint256 private constant BPS = 10_000;
|
|
71
|
+
/// @dev Gas given to optional policy/verifier queries, so a misbehaving contract cannot burn
|
|
72
|
+
/// the caller's gas; a query that fails or runs out is treated as "no fee" / "unsupported".
|
|
73
|
+
uint256 private constant QUERY_GAS = 50_000;
|
|
74
|
+
/// @dev How far ahead of the block a proof's issue time may be (clock skew between the token
|
|
75
|
+
/// issuer and the chain); proofs are made after their token is issued, so this is slack only.
|
|
76
|
+
uint64 private constant FUTURE_SLACK = 15 minutes;
|
|
77
|
+
|
|
78
|
+
/// @notice Deployer (PviumP2IdVaultFactory): the policy's source, and the only caller of initialize() and fundFor().
|
|
79
|
+
address public immutable factory;
|
|
80
|
+
bytes32 public nsHash;
|
|
81
|
+
bytes32 public saltCommitment;
|
|
82
|
+
uint64 public minRefundWindow;
|
|
83
|
+
uint64 public maxRefundWindow;
|
|
84
|
+
bool private _initialized;
|
|
85
|
+
|
|
86
|
+
/// @notice Wallet of the newest proof presented under a verifier; paid by that verifier's default-bucket sweeps.
|
|
87
|
+
mapping(address verifier => address) public owner;
|
|
88
|
+
/// @notice Issue time of that proof. Older proofs are refused under that verifier on every path.
|
|
89
|
+
mapping(address verifier => uint64) public latestProofIat;
|
|
90
|
+
/// @notice The verifier's `revision()` when its owner was cached. A cache from an older revision
|
|
91
|
+
/// is void: the verifier revoked something it trusted then, so the owner must re-prove.
|
|
92
|
+
mapping(address verifier => uint64) public ownerRevision;
|
|
93
|
+
/// @notice Freshness floor for direct transfers: the newest proof presented through the verifier
|
|
94
|
+
/// that was the default at the time. Carries across default changes.
|
|
95
|
+
uint64 public untrackedProofIat;
|
|
96
|
+
|
|
97
|
+
/// @notice All deposits ever made, by id.
|
|
98
|
+
Deposit[] public deposits;
|
|
99
|
+
|
|
100
|
+
/// @notice Unconsumed total per (verifier, constraint, token) bucket.
|
|
101
|
+
mapping(address verifier => mapping(bytes32 constraint => mapping(address token => uint256))) public bucketTotal;
|
|
102
|
+
/// @notice Sum of all unconsumed deposits per token.
|
|
103
|
+
mapping(address token => uint256) public trackedTotal;
|
|
104
|
+
/// @dev Deposit ids per bucket, in funding order.
|
|
105
|
+
mapping(address verifier => mapping(bytes32 constraint => mapping(address token => uint256[]))) private _bucketDeposits;
|
|
106
|
+
/// @notice Index into the bucket's deposit list below which every deposit is consumed.
|
|
107
|
+
mapping(address verifier => mapping(bytes32 constraint => mapping(address token => uint256))) public bucketCursor;
|
|
108
|
+
/// @dev keccak256(constraint, funder) => depositId + 1. A funder can use a constraint once, so a
|
|
109
|
+
/// signature over it releases at most one of their deposits; another funder reusing the
|
|
110
|
+
/// same value only puts their own money in the bucket.
|
|
111
|
+
mapping(bytes32 => uint256) private _constraintDeposit;
|
|
112
|
+
/// @notice Fees accrued and not yet distributed, per verifier they were earned through and token.
|
|
113
|
+
mapping(address verifier => mapping(address token => uint256)) public feesOwed;
|
|
114
|
+
/// @notice Sum of feesOwed per token: held for distribution, never part of a payout.
|
|
115
|
+
mapping(address token => uint256) public feesOwedTotal;
|
|
116
|
+
|
|
117
|
+
uint256 private reentrancyLock = 1;
|
|
118
|
+
|
|
119
|
+
error InvalidRefundWindow();
|
|
120
|
+
error VerifierNotApproved(address verifier);
|
|
121
|
+
error ConstraintsUnsupported(address verifier);
|
|
122
|
+
error InvalidRefundAmount();
|
|
123
|
+
error InvalidToken();
|
|
124
|
+
error OwnerNotInitialized();
|
|
125
|
+
error OwnerRevoked();
|
|
126
|
+
error ProofFromFuture();
|
|
127
|
+
error ProofTooOld();
|
|
128
|
+
error InvalidWallet();
|
|
129
|
+
error NotFunder();
|
|
130
|
+
error DepositConsumed();
|
|
131
|
+
error DepositNotInBucket(uint256 depositId);
|
|
132
|
+
error ConstraintRequired();
|
|
133
|
+
error ConstraintUsed(uint256 depositId);
|
|
134
|
+
error NotFactory();
|
|
135
|
+
error AlreadyInitialized();
|
|
136
|
+
error RefundNotReady();
|
|
137
|
+
error TokenCallFailed();
|
|
138
|
+
error TokenBalanceQueryFailed();
|
|
139
|
+
error AmountTooLarge();
|
|
140
|
+
error Reentrancy();
|
|
141
|
+
error FeeOverdrawn();
|
|
142
|
+
error NativeValueMismatch();
|
|
143
|
+
error NativeTransferFailed();
|
|
144
|
+
|
|
145
|
+
/// @dev No constructor arguments, so the creation code is identical for every vault and
|
|
146
|
+
/// `keccak256(creationCode)` is a constant anyone can use to derive an identity's vault
|
|
147
|
+
/// address from the factory address. The deployer is recorded and must call initialize().
|
|
148
|
+
constructor() {
|
|
149
|
+
factory = msg.sender;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// @notice Plain native-coin sends (e.g. fee payouts from a launchpad) are accepted as direct
|
|
153
|
+
/// transfers: untracked, claimable through the default verifier, never refundable.
|
|
154
|
+
/// Deliberately empty, so it works with the 2300-gas stipend of `transfer`/`send`.
|
|
155
|
+
receive() external payable {}
|
|
156
|
+
|
|
157
|
+
/// @notice Set by the factory once, immediately after deployment.
|
|
158
|
+
function initialize(
|
|
159
|
+
bytes32 _nsHash,
|
|
160
|
+
bytes32 _saltCommitment,
|
|
161
|
+
uint64 _minRefundWindow,
|
|
162
|
+
uint64 _maxRefundWindow
|
|
163
|
+
) external onlyFactory {
|
|
164
|
+
if (_initialized) revert AlreadyInitialized();
|
|
165
|
+
if (_minRefundWindow > _maxRefundWindow) revert InvalidRefundWindow();
|
|
166
|
+
_initialized = true;
|
|
167
|
+
nsHash = _nsHash;
|
|
168
|
+
saltCommitment = _saltCommitment;
|
|
169
|
+
minRefundWindow = _minRefundWindow;
|
|
170
|
+
maxRefundWindow = _maxRefundWindow;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ------------------------------------------------------------------ funding
|
|
174
|
+
|
|
175
|
+
/// @notice Fund under the factory's default verifier.
|
|
176
|
+
/// @param token NATIVE for the native coin (send it as msg.value, equal to `amount`), otherwise
|
|
177
|
+
/// an ERC-20 pulled with transferFrom (approve first; send no value).
|
|
178
|
+
/// @param constraint Opaque bytes32 the verifier must see satisfied before this deposit can
|
|
179
|
+
/// be swept (e.g. a screening commitment); bytes32(0) for the default bucket.
|
|
180
|
+
/// @param ref Opaque application reference emitted in Funded; bytes32(0) for none.
|
|
181
|
+
function fund(
|
|
182
|
+
address token,
|
|
183
|
+
uint256 amount,
|
|
184
|
+
bytes32 constraint,
|
|
185
|
+
uint64 refundWindow,
|
|
186
|
+
bytes32 ref
|
|
187
|
+
) external payable nonReentrant returns (uint256 depositId) {
|
|
188
|
+
return _fund(msg.sender, defaultVerifier(), token, amount, constraint, refundWindow, ref);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// @notice Fund under any verifier the factory's policy allows. Native coin as in fund().
|
|
192
|
+
/// @param ref Opaque application reference emitted in Funded; bytes32(0) for none.
|
|
193
|
+
function fundWith(
|
|
194
|
+
address verifier,
|
|
195
|
+
address token,
|
|
196
|
+
uint256 amount,
|
|
197
|
+
bytes32 constraint,
|
|
198
|
+
uint64 refundWindow,
|
|
199
|
+
bytes32 ref
|
|
200
|
+
) external payable nonReentrant returns (uint256 depositId) {
|
|
201
|
+
return _fund(msg.sender, verifier, token, amount, constraint, refundWindow, ref);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// @notice Factory-only: fund on behalf of `funder`, who keeps the refund right. Tokens are
|
|
205
|
+
/// pulled from the factory, which has already collected them from `funder`; native
|
|
206
|
+
/// coin arrives as msg.value.
|
|
207
|
+
/// @param ref Opaque application reference emitted in Funded; bytes32(0) for none.
|
|
208
|
+
function fundFor(
|
|
209
|
+
address funder,
|
|
210
|
+
address verifier,
|
|
211
|
+
address token,
|
|
212
|
+
uint256 amount,
|
|
213
|
+
bytes32 constraint,
|
|
214
|
+
uint64 refundWindow,
|
|
215
|
+
bytes32 ref
|
|
216
|
+
) external payable nonReentrant onlyFactory returns (uint256 depositId) {
|
|
217
|
+
if (funder == address(0)) revert InvalidWallet();
|
|
218
|
+
return _fund(funder, verifier, token, amount, constraint, refundWindow, ref);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// @dev Take `amount` of `token` from msg.sender (native: exactly msg.value; ERC-20: pulled,
|
|
222
|
+
/// crediting what actually arrived) and record a deposit owned by `funder`, with the fee
|
|
223
|
+
/// rate the policy quotes now (capped) fixed for it.
|
|
224
|
+
function _fund(
|
|
225
|
+
address funder,
|
|
226
|
+
address verifier,
|
|
227
|
+
address token,
|
|
228
|
+
uint256 amount,
|
|
229
|
+
bytes32 constraint,
|
|
230
|
+
uint64 refundWindow,
|
|
231
|
+
bytes32 ref
|
|
232
|
+
) private onlyAllowed(verifier) returns (uint256 depositId) {
|
|
233
|
+
bytes32 constraintKey;
|
|
234
|
+
if (constraint != bytes32(0)) {
|
|
235
|
+
if (!_supportsConstraints(verifier)) revert ConstraintsUnsupported(verifier);
|
|
236
|
+
constraintKey = keccak256(abi.encode(constraint, funder));
|
|
237
|
+
if (_constraintDeposit[constraintKey] != 0) revert ConstraintUsed(_constraintDeposit[constraintKey] - 1);
|
|
238
|
+
}
|
|
239
|
+
if (refundWindow < minRefundWindow || refundWindow > maxRefundWindow) revert InvalidRefundWindow();
|
|
240
|
+
if (amount == 0 || amount > type(uint128).max) revert InvalidRefundAmount();
|
|
241
|
+
|
|
242
|
+
uint256 credited;
|
|
243
|
+
if (token == NATIVE) {
|
|
244
|
+
if (msg.value != amount) revert NativeValueMismatch();
|
|
245
|
+
credited = amount;
|
|
246
|
+
} else {
|
|
247
|
+
if (msg.value != 0) revert NativeValueMismatch(); // never strand native coin on an ERC-20 deposit
|
|
248
|
+
if (token.code.length == 0) revert InvalidToken();
|
|
249
|
+
uint256 beforeBalance = _balanceOf(token);
|
|
250
|
+
_callToken(token, abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, address(this), amount));
|
|
251
|
+
uint256 afterBalance = _balanceOf(token);
|
|
252
|
+
if (afterBalance <= beforeBalance) revert InvalidRefundAmount();
|
|
253
|
+
credited = afterBalance - beforeBalance;
|
|
254
|
+
if (credited > type(uint128).max) revert AmountTooLarge();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
uint16 feeBps = _quoteFeeBps(verifier, token);
|
|
258
|
+
depositId = deposits.length;
|
|
259
|
+
deposits.push(
|
|
260
|
+
Deposit({
|
|
261
|
+
funder: funder,
|
|
262
|
+
fundedAt: uint64(block.timestamp),
|
|
263
|
+
consumed: false,
|
|
264
|
+
token: token,
|
|
265
|
+
refundWindow: refundWindow,
|
|
266
|
+
verifier: verifier,
|
|
267
|
+
feeBps: feeBps,
|
|
268
|
+
amount: uint128(credited),
|
|
269
|
+
constraint: constraint
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
if (constraint != bytes32(0)) _constraintDeposit[constraintKey] = depositId + 1;
|
|
273
|
+
_bucketDeposits[verifier][constraint][token].push(depositId);
|
|
274
|
+
bucketTotal[verifier][constraint][token] += credited;
|
|
275
|
+
trackedTotal[token] += credited;
|
|
276
|
+
emit Funded(depositId, funder, token, credited, verifier, constraint, refundWindow, feeBps, ref);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// @notice Return an unconsumed deposit to its funder after its refund window. Never charges a
|
|
280
|
+
/// fee and never consults the policy.
|
|
281
|
+
function refund(uint256 depositId) external nonReentrant {
|
|
282
|
+
Deposit storage deposit = deposits[depositId];
|
|
283
|
+
if (deposit.funder != msg.sender) revert NotFunder();
|
|
284
|
+
if (deposit.consumed) revert DepositConsumed();
|
|
285
|
+
if (block.timestamp <= uint256(deposit.fundedAt) + deposit.refundWindow) revert RefundNotReady();
|
|
286
|
+
|
|
287
|
+
_consume(deposit);
|
|
288
|
+
_transfer(deposit.token, msg.sender, deposit.amount);
|
|
289
|
+
emit Refunded(depositId, msg.sender, deposit.token, deposit.amount);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ------------------------------------------------------------------ proofs
|
|
293
|
+
|
|
294
|
+
/// @notice Present a proof under `verifier` without claiming: sets that verifier's owner
|
|
295
|
+
/// wallet from it if it is newer than anything seen before, and retires every older
|
|
296
|
+
/// proof. Call this immediately after moving to a new wallet.
|
|
297
|
+
function refreshProof(address verifier, bytes calldata proof) external nonReentrant {
|
|
298
|
+
_present(verifier, proof, _noConstraint());
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// @notice Present a proof and sweep `verifier`'s default bucket for `token` in one call (first claim).
|
|
302
|
+
/// @param depositCountLimit Number of deposit records to visit from the bucket cursor (not
|
|
303
|
+
/// an amount, not an index); 0 = all. For the default verifier, untracked
|
|
304
|
+
/// (bare-transfer) funds are always included.
|
|
305
|
+
function refreshProofAndSweep(
|
|
306
|
+
address verifier,
|
|
307
|
+
bytes calldata proof,
|
|
308
|
+
address token,
|
|
309
|
+
uint256 depositCountLimit
|
|
310
|
+
) external nonReentrant returns (uint256 amount, uint256 consumed) {
|
|
311
|
+
_present(verifier, proof, _noConstraint());
|
|
312
|
+
return _sweepDefault(verifier, token, depositCountLimit);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ------------------------------------------------------------------ claiming
|
|
316
|
+
|
|
317
|
+
/// @notice Sweep `verifier`'s default bucket for `token` to that verifier's owner. Walks only
|
|
318
|
+
/// this bucket's list from its cursor; `depositCountLimit` records per call (0 = all).
|
|
319
|
+
/// When `verifier` is the factory default, untracked funds are included.
|
|
320
|
+
function sweep(address verifier, address token, uint256 depositCountLimit)
|
|
321
|
+
external
|
|
322
|
+
nonReentrant
|
|
323
|
+
returns (uint256 amount, uint256 consumed)
|
|
324
|
+
{
|
|
325
|
+
return _sweepDefault(verifier, token, depositCountLimit); // checks the owner is set and current
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// @notice Sweep only untracked funds (bare ERC-20 transfers backed by no deposit record) to
|
|
329
|
+
/// the default verifier's owner. The fee rate is quoted now.
|
|
330
|
+
function sweepUntracked(address token) external nonReentrant returns (uint256 amount) {
|
|
331
|
+
address verifier = defaultVerifier();
|
|
332
|
+
address to = _ownerOf(verifier);
|
|
333
|
+
if (latestProofIat[verifier] < untrackedProofIat) revert ProofTooOld();
|
|
334
|
+
uint256 gross = _untrackedBalance(token);
|
|
335
|
+
uint256 fee = (gross * _quoteFeeBps(verifier, token)) / BPS;
|
|
336
|
+
uint256 charged;
|
|
337
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
338
|
+
emit Swept(verifier, token, amount, charged, to, 0);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/// @notice Sweep specific default-bucket deposits of `verifier` by id (e.g. to skip spam in the same bucket).
|
|
342
|
+
function sweepDeposits(address verifier, address token, uint256[] calldata depositIds)
|
|
343
|
+
external
|
|
344
|
+
nonReentrant
|
|
345
|
+
onlyInitialized(verifier)
|
|
346
|
+
returns (uint256 amount)
|
|
347
|
+
{
|
|
348
|
+
address to = owner[verifier];
|
|
349
|
+
(uint256 gross, uint256 fee) = _consumeIds(verifier, bytes32(0), token, depositIds, to);
|
|
350
|
+
uint256 charged;
|
|
351
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
352
|
+
emit Swept(verifier, token, amount, charged, to, depositIds.length);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// @notice Sweep the bucket funded under `verifier` and `constraint.commitment`. The verifier
|
|
356
|
+
/// must accept both the identity proof and the constraint evidence; funds go to the
|
|
357
|
+
/// wallet the proof resolves to. Default buckets (zero commitment) are swept via sweep().
|
|
358
|
+
function sweepBucket(
|
|
359
|
+
address verifier,
|
|
360
|
+
IP2IDVerifier.Constraint calldata constraint,
|
|
361
|
+
address token,
|
|
362
|
+
bytes calldata proof,
|
|
363
|
+
uint256 depositCountLimit
|
|
364
|
+
) external nonReentrant returns (uint256 amount, uint256 consumed) {
|
|
365
|
+
address to = _verifyForThisVault(verifier, proof, constraint);
|
|
366
|
+
uint256 gross;
|
|
367
|
+
uint256 fee;
|
|
368
|
+
(gross, fee, consumed) = _consumeFromCursor(verifier, constraint.commitment, token, depositCountLimit, to);
|
|
369
|
+
uint256 charged;
|
|
370
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
371
|
+
emit SweptBucket(verifier, constraint.commitment, token, amount, charged, to, consumed);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/// @notice Sweep specific deposits of a constrained bucket by id.
|
|
375
|
+
function sweepBucketDeposits(
|
|
376
|
+
address verifier,
|
|
377
|
+
IP2IDVerifier.Constraint calldata constraint,
|
|
378
|
+
address token,
|
|
379
|
+
uint256[] calldata depositIds,
|
|
380
|
+
bytes calldata proof
|
|
381
|
+
) external nonReentrant returns (uint256 amount) {
|
|
382
|
+
address to = _verifyForThisVault(verifier, proof, constraint);
|
|
383
|
+
(uint256 gross, uint256 fee) = _consumeIds(verifier, constraint.commitment, token, depositIds, to);
|
|
384
|
+
uint256 charged;
|
|
385
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
386
|
+
emit SweptBucket(verifier, constraint.commitment, token, amount, charged, to, depositIds.length);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ------------------------------------------------------------------ fees
|
|
390
|
+
|
|
391
|
+
/// @notice Hand the fees earned through `verifier` in `token` to the current policy, which
|
|
392
|
+
/// pulls them and distributes them. Anyone may call it: the tokens can only go to the
|
|
393
|
+
/// policy, and only as much as is owed. Returns the amount the policy took.
|
|
394
|
+
function withdrawFees(address verifier, address token) external nonReentrant returns (uint256 amount) {
|
|
395
|
+
uint256 owed = feesOwed[verifier][token];
|
|
396
|
+
if (owed == 0) return 0;
|
|
397
|
+
address pol = policy();
|
|
398
|
+
if (token == NATIVE) {
|
|
399
|
+
// Native coin cannot be pulled: it is sent along with the call, exactly the amount owed.
|
|
400
|
+
IP2IDPolicy(pol).distributeFee{value: owed}(verifier, token, owed);
|
|
401
|
+
amount = owed;
|
|
402
|
+
} else {
|
|
403
|
+
uint256 before = _balanceOf(token);
|
|
404
|
+
_callToken(token, abi.encodeWithSignature("approve(address,uint256)", pol, owed));
|
|
405
|
+
IP2IDPolicy(pol).distributeFee(verifier, token, owed);
|
|
406
|
+
_callToken(token, abi.encodeWithSignature("approve(address,uint256)", pol, 0));
|
|
407
|
+
uint256 afterBalance = _balanceOf(token);
|
|
408
|
+
amount = before > afterBalance ? before - afterBalance : 0;
|
|
409
|
+
if (amount > owed) revert FeeOverdrawn();
|
|
410
|
+
}
|
|
411
|
+
feesOwed[verifier][token] = owed - amount;
|
|
412
|
+
feesOwedTotal[token] -= amount;
|
|
413
|
+
emit FeesDistributed(verifier, token, amount, pol);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ------------------------------------------------------------------ views
|
|
417
|
+
|
|
418
|
+
/// @inheritdoc IP2IDVault
|
|
419
|
+
function p2idVersion() external pure returns (string memory) {
|
|
420
|
+
return "p2id.vault.v1";
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// @notice The factory's current policy (replaceable there only through a timelock).
|
|
424
|
+
function policy() public view returns (address) {
|
|
425
|
+
return IP2IdVaultFactory(factory).policy();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/// @notice The factory's current default verifier (timelocked there): used by fund() and for untracked funds.
|
|
429
|
+
function defaultVerifier() public view returns (address) {
|
|
430
|
+
return IP2IdVaultFactory(factory).defaultVerifier();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function depositCount() external view returns (uint256) {
|
|
434
|
+
return deposits.length;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/// @notice Deposit ids in a bucket, in funding order (consumed ones included; see `deposits`).
|
|
438
|
+
function bucketDepositIds(address verifier, bytes32 constraint, address token) external view returns (uint256[] memory) {
|
|
439
|
+
return _bucketDeposits[verifier][constraint][token];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function bucketDepositCount(address verifier, bytes32 constraint, address token) external view returns (uint256) {
|
|
443
|
+
return _bucketDeposits[verifier][constraint][token].length;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/// @notice The deposit `funder` made under `constraint`, if any. Each funder can use a constraint once.
|
|
447
|
+
function constraintDeposit(bytes32 constraint, address funder) external view returns (bool used, uint256 depositId) {
|
|
448
|
+
uint256 stored = _constraintDeposit[keccak256(abi.encode(constraint, funder))];
|
|
449
|
+
return stored == 0 ? (false, 0) : (true, stored - 1);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/// @notice Gross amount `sweep(verifier, token, 0)` would release now, before fees: every
|
|
453
|
+
/// unconsumed default deposit under `verifier`, plus untracked funds when it is the default verifier.
|
|
454
|
+
function sweepable(address verifier, address token) external view returns (uint256) {
|
|
455
|
+
uint256 amount = bucketTotal[verifier][bytes32(0)][token];
|
|
456
|
+
if (_paysUntracked(verifier)) amount += _untrackedBalance(token);
|
|
457
|
+
return amount;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/// @notice Untracked funds only: bare ERC-20 transfers not backed by any deposit record.
|
|
461
|
+
function untrackedBalance(address token) external view returns (uint256) {
|
|
462
|
+
return _untrackedBalance(token);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ------------------------------------------------------------------ internals
|
|
466
|
+
|
|
467
|
+
/// @dev Every proof enters through here. Requires `verifier` to be allowed, verifies the proof
|
|
468
|
+
/// for this vault's identity (the verifier reverts for any other identity), refuses it if
|
|
469
|
+
/// older than the newest proof seen under that verifier, and if it is newer makes its
|
|
470
|
+
/// wallet that verifier's owner. Returns the wallet to pay.
|
|
471
|
+
function _present(
|
|
472
|
+
address verifier,
|
|
473
|
+
bytes calldata proof,
|
|
474
|
+
IP2IDVerifier.Constraint memory constraint
|
|
475
|
+
) private onlyClaimable(verifier) returns (address wallet) {
|
|
476
|
+
uint64 iat;
|
|
477
|
+
(wallet, iat) = IP2IDVerifier(verifier).getIdentityWallet(saltCommitment, proof, constraint);
|
|
478
|
+
if (wallet == address(0)) revert InvalidWallet();
|
|
479
|
+
// A forged token could carry any issue time; refusing future ones (beyond clock skew) bounds
|
|
480
|
+
// how far a forged proof can push the freshness ratchets, so a real owner recovers with a
|
|
481
|
+
// token issued minutes later rather than days.
|
|
482
|
+
if (iat > block.timestamp + FUTURE_SLACK) revert ProofFromFuture();
|
|
483
|
+
uint64 rev = _revision(verifier);
|
|
484
|
+
bool fresh = ownerRevision[verifier] == rev;
|
|
485
|
+
// The ratchet survives a revision change: a void cache pays nobody, but proofs it already
|
|
486
|
+
// retired stay retired, so re-proving takes a proof at least as recent as the last one.
|
|
487
|
+
uint64 latest = latestProofIat[verifier];
|
|
488
|
+
if (iat < latest) revert ProofTooOld();
|
|
489
|
+
if (iat > latest || owner[verifier] == address(0)) {
|
|
490
|
+
owner[verifier] = wallet;
|
|
491
|
+
latestProofIat[verifier] = iat;
|
|
492
|
+
ownerRevision[verifier] = rev;
|
|
493
|
+
emit OwnerRefreshed(verifier, wallet, iat);
|
|
494
|
+
} else if (!fresh && wallet == owner[verifier]) {
|
|
495
|
+
// Re-validating the recorded wallet after a revision change: the same proof (or one of
|
|
496
|
+
// the same age for the same wallet) restores it. Changing wallets takes a newer proof.
|
|
497
|
+
ownerRevision[verifier] = rev;
|
|
498
|
+
emit OwnerRefreshed(verifier, wallet, iat);
|
|
499
|
+
}
|
|
500
|
+
if (iat > untrackedProofIat && verifier == defaultVerifier()) untrackedProofIat = iat;
|
|
501
|
+
// Otherwise iat == latest with the owner set: a same-age proof (a replayed copy, or another
|
|
502
|
+
// wallet slot of the same token). The owner stays, and constrained paths pay `wallet`.
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/// @dev Constrained-bucket entry: default buckets are only reachable through sweep().
|
|
506
|
+
function _verifyForThisVault(
|
|
507
|
+
address verifier,
|
|
508
|
+
bytes calldata proof,
|
|
509
|
+
IP2IDVerifier.Constraint calldata constraint
|
|
510
|
+
) private returns (address wallet) {
|
|
511
|
+
if (constraint.commitment == bytes32(0)) revert ConstraintRequired();
|
|
512
|
+
return _present(verifier, proof, constraint);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/// @dev Direct transfers go to `verifier`'s owner only if it is the current default and its
|
|
516
|
+
/// owner proof is at least as fresh as the newest proof any default has seen.
|
|
517
|
+
function _paysUntracked(address verifier) private view returns (bool) {
|
|
518
|
+
return verifier == defaultVerifier() && _ownerFresh(verifier) && latestProofIat[verifier] >= untrackedProofIat;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/// @dev Owner wallet for proof-less default sweeps; the verifier must still be claimable and
|
|
522
|
+
/// must not have revoked anything since the owner was proven.
|
|
523
|
+
function _ownerOf(address verifier) private view onlyClaimable(verifier) returns (address to) {
|
|
524
|
+
to = owner[verifier];
|
|
525
|
+
if (to == address(0)) revert OwnerNotInitialized();
|
|
526
|
+
if (!_ownerFresh(verifier)) revert OwnerRevoked();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/// @dev Whether the cached owner was proven under the verifier's current revision.
|
|
530
|
+
function _ownerFresh(address verifier) private view returns (bool) {
|
|
531
|
+
return ownerRevision[verifier] == _revision(verifier);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function _noConstraint() private pure returns (IP2IDVerifier.Constraint memory c) {
|
|
535
|
+
c.commitment = bytes32(0);
|
|
536
|
+
c.signature = "";
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/// @dev Default-bucket sweep for one verifier: the default deposits consumed here at their
|
|
540
|
+
/// fixed rates, plus the untracked surplus (at the rate quoted now) when `verifier` is the
|
|
541
|
+
/// factory default.
|
|
542
|
+
function _sweepDefault(address verifier, address token, uint256 depositCountLimit)
|
|
543
|
+
private
|
|
544
|
+
returns (uint256 amount, uint256 consumed)
|
|
545
|
+
{
|
|
546
|
+
// Checked here, not only in sweep(): refreshProofAndSweep reaches this after a proof that
|
|
547
|
+
// may have verified without restoring a void cache (an equal-time proof for another wallet).
|
|
548
|
+
address to = _ownerOf(verifier);
|
|
549
|
+
uint256 untracked = _paysUntracked(verifier) ? _untrackedBalance(token) : 0;
|
|
550
|
+
uint256 gross;
|
|
551
|
+
uint256 fee;
|
|
552
|
+
(gross, fee, consumed) = _consumeFromCursor(verifier, bytes32(0), token, depositCountLimit, to);
|
|
553
|
+
if (untracked != 0) {
|
|
554
|
+
gross += untracked;
|
|
555
|
+
fee += (untracked * _quoteFeeBps(verifier, token)) / BPS;
|
|
556
|
+
}
|
|
557
|
+
uint256 charged;
|
|
558
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
559
|
+
emit Swept(verifier, token, amount, charged, to, consumed);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function _consumeFromCursor(
|
|
563
|
+
address verifier,
|
|
564
|
+
bytes32 constraint,
|
|
565
|
+
address token,
|
|
566
|
+
uint256 depositCountLimit,
|
|
567
|
+
address to
|
|
568
|
+
) private returns (uint256 amount, uint256 fee, uint256 consumed) {
|
|
569
|
+
uint256[] storage ids = _bucketDeposits[verifier][constraint][token];
|
|
570
|
+
uint256 i = bucketCursor[verifier][constraint][token];
|
|
571
|
+
uint256 end = ids.length;
|
|
572
|
+
// Bound by records visited, not records consumed: a run of already-consumed records
|
|
573
|
+
// (refunded, or swept by id) is paged through at a fixed cost per call.
|
|
574
|
+
if (depositCountLimit != 0 && end - i > depositCountLimit) end = i + depositCountLimit;
|
|
575
|
+
while (i < end) {
|
|
576
|
+
uint256 id = ids[i];
|
|
577
|
+
Deposit storage deposit = deposits[id];
|
|
578
|
+
if (!deposit.consumed) {
|
|
579
|
+
(uint256 a, uint256 f) = _claim(id, deposit, to);
|
|
580
|
+
amount += a;
|
|
581
|
+
fee += f;
|
|
582
|
+
consumed++;
|
|
583
|
+
}
|
|
584
|
+
i++;
|
|
585
|
+
}
|
|
586
|
+
bucketCursor[verifier][constraint][token] = i;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function _consumeIds(
|
|
590
|
+
address verifier,
|
|
591
|
+
bytes32 constraint,
|
|
592
|
+
address token,
|
|
593
|
+
uint256[] calldata depositIds,
|
|
594
|
+
address to
|
|
595
|
+
) private returns (uint256 amount, uint256 fee) {
|
|
596
|
+
for (uint256 k = 0; k < depositIds.length; k++) {
|
|
597
|
+
uint256 id = depositIds[k];
|
|
598
|
+
if (id >= deposits.length) revert DepositNotInBucket(id);
|
|
599
|
+
Deposit storage deposit = deposits[id];
|
|
600
|
+
if (deposit.verifier != verifier || deposit.constraint != constraint || deposit.token != token) {
|
|
601
|
+
revert DepositNotInBucket(id);
|
|
602
|
+
}
|
|
603
|
+
if (deposit.consumed) revert DepositConsumed();
|
|
604
|
+
(uint256 a, uint256 f) = _claim(id, deposit, to);
|
|
605
|
+
amount += a;
|
|
606
|
+
fee += f;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/// @dev Consume a deposit for a payout to `to`; its fee is computed at the rate fixed at funding.
|
|
611
|
+
function _claim(uint256 id, Deposit storage deposit, address to) private returns (uint256 amount, uint256 fee) {
|
|
612
|
+
_consume(deposit);
|
|
613
|
+
amount = deposit.amount;
|
|
614
|
+
fee = (amount * deposit.feeBps) / BPS;
|
|
615
|
+
emit Claimed(id, to, amount, fee);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/// @dev Pay `gross - fee` to `to` and keep `fee` accrued for `verifier`. No policy call.
|
|
619
|
+
/// Returns (net paid, fee charged).
|
|
620
|
+
function _payout(address verifier, address token, address to, uint256 gross, uint256 fee)
|
|
621
|
+
private
|
|
622
|
+
returns (uint256 net, uint256 charged)
|
|
623
|
+
{
|
|
624
|
+
if (fee != 0) {
|
|
625
|
+
charged = fee;
|
|
626
|
+
feesOwed[verifier][token] += fee;
|
|
627
|
+
feesOwedTotal[token] += fee;
|
|
628
|
+
emit FeeAccrued(verifier, token, fee);
|
|
629
|
+
}
|
|
630
|
+
net = gross - charged;
|
|
631
|
+
if (net != 0) _transfer(token, to, net);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function _untrackedBalance(address token) private view returns (uint256) {
|
|
635
|
+
uint256 balance = _balanceOf(token);
|
|
636
|
+
uint256 held = trackedTotal[token] + feesOwedTotal[token];
|
|
637
|
+
return balance > held ? balance - held : 0;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function _consume(Deposit storage deposit) private {
|
|
641
|
+
deposit.consumed = true;
|
|
642
|
+
bucketTotal[deposit.verifier][deposit.constraint][deposit.token] -= deposit.amount;
|
|
643
|
+
trackedTotal[deposit.token] -= deposit.amount;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ------------------------------------------------------------------ policy / verifier queries
|
|
647
|
+
|
|
648
|
+
function _policy() private view returns (IP2IDPolicy) {
|
|
649
|
+
return IP2IDPolicy(IP2IdVaultFactory(factory).policy());
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/// @dev The policy's fee rate for (verifier, token), capped at MAX_FEE_BPS; 0 if the query fails.
|
|
653
|
+
function _quoteFeeBps(address verifier, address token) private view returns (uint16) {
|
|
654
|
+
(bool ok, uint256 v) = _query(address(_policy()), abi.encodeCall(IP2IDPolicy.feeBps, (verifier, token)));
|
|
655
|
+
if (!ok) return 0;
|
|
656
|
+
return v > MAX_FEE_BPS ? MAX_FEE_BPS : uint16(v);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/// @dev The verifier's revision; 0 if it does not report one (nothing it trusts is revocable).
|
|
660
|
+
function _revision(address verifier) private view returns (uint64) {
|
|
661
|
+
(bool ok, uint256 v) = _query(verifier, abi.encodeCall(IP2IDVerifier.revision, ()));
|
|
662
|
+
return ok ? uint64(v) : 0;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/// @dev Whether `verifier` declares it can satisfy constraints; false if it does not say.
|
|
666
|
+
function _supportsConstraints(address verifier) private view returns (bool) {
|
|
667
|
+
(bool ok, uint256 v) = _query(verifier, abi.encodeCall(IP2IDVerifier.supportsConstraints, ()));
|
|
668
|
+
return ok && v == 1;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/// @dev Gas-capped static call expecting one 32-byte word; (false, 0) on any failure.
|
|
672
|
+
function _query(address target, bytes memory data) private view returns (bool ok, uint256 value) {
|
|
673
|
+
bytes memory ret;
|
|
674
|
+
(ok, ret) = target.staticcall{gas: QUERY_GAS}(data);
|
|
675
|
+
if (!ok || ret.length != 32) return (false, 0);
|
|
676
|
+
value = abi.decode(ret, (uint256));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// ------------------------------------------------------------------ tokens
|
|
680
|
+
|
|
681
|
+
/// @dev Pay out `amount` of `token`; native coin goes with all remaining gas (every caller
|
|
682
|
+
/// holds the reentrancy lock), so smart-contract wallets can receive it.
|
|
683
|
+
function _transfer(address token, address to, uint256 amount) private {
|
|
684
|
+
if (token == NATIVE) {
|
|
685
|
+
(bool ok, ) = payable(to).call{value: amount}("");
|
|
686
|
+
if (!ok) revert NativeTransferFailed();
|
|
687
|
+
} else {
|
|
688
|
+
_callToken(token, abi.encodeWithSignature("transfer(address,uint256)", to, amount));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function _balanceOf(address token) private view returns (uint256 balance) {
|
|
693
|
+
if (token == NATIVE) return address(this).balance;
|
|
694
|
+
(bool ok, bytes memory data) = token.staticcall(abi.encodeWithSignature("balanceOf(address)", address(this)));
|
|
695
|
+
if (!ok || data.length != 32) revert TokenBalanceQueryFailed();
|
|
696
|
+
balance = abi.decode(data, (uint256));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function _callToken(address token, bytes memory input) private {
|
|
700
|
+
(bool ok, bytes memory data) = token.call(input);
|
|
701
|
+
if (!ok || (data.length != 0 && (data.length != 32 || !abi.decode(data, (bool))))) revert TokenCallFailed();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// ------------------------------------------------------------------ modifiers
|
|
705
|
+
|
|
706
|
+
/// @dev Funding: the policy decides which verifiers new deposits may be made under.
|
|
707
|
+
modifier onlyAllowed(address verifier) {
|
|
708
|
+
if (!_policy().isVerifierAllowed(verifier)) revert VerifierNotApproved(verifier);
|
|
709
|
+
_;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/// @dev Claiming: through the factory's current default verifier always (see the contract
|
|
713
|
+
/// notes), otherwise only while the policy allows the verifier.
|
|
714
|
+
modifier onlyClaimable(address verifier) {
|
|
715
|
+
if (verifier != defaultVerifier() && !_policy().isVerifierAllowed(verifier)) revert VerifierNotApproved(verifier);
|
|
716
|
+
_;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
modifier onlyFactory() {
|
|
720
|
+
if (msg.sender != factory) revert NotFactory();
|
|
721
|
+
_;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/// @dev Proof-less default-bucket payouts need an owner wallet under an allowed verifier.
|
|
725
|
+
modifier onlyInitialized(address verifier) {
|
|
726
|
+
_ownerOf(verifier);
|
|
727
|
+
_;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
modifier nonReentrant() {
|
|
731
|
+
if (reentrancyLock != 1) revert Reentrancy();
|
|
732
|
+
reentrancyLock = 2;
|
|
733
|
+
_;
|
|
734
|
+
reentrancyLock = 1;
|
|
735
|
+
}
|
|
736
|
+
}
|