@pvium/p2id-core 0.1.1 → 0.1.3
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 +2 -0
- package/contracts/P2IDVault.sol +119 -140
- package/contracts/PviumIdentity.sol +23 -36
- package/contracts/PviumP2IDPolicy.sol +4 -7
- package/contracts/PviumP2IDVaultProxy.sol +169 -0
- package/contracts/PviumP2IdVaultFactory.sol +142 -28
- package/contracts/PviumVerifier.sol +13 -21
- package/contracts/interfaces/IP2IDPolicy.sol +5 -21
- package/contracts/interfaces/IP2IDVault.sol +24 -17
- package/contracts/interfaces/IP2IDVaultProxy.sol +38 -0
- package/contracts/interfaces/IP2IDVerifier.sol +9 -13
- package/contracts/interfaces/IP2IdVaultFactory.sol +34 -7
- package/contracts/interfaces/IPviumIdentity.sol +5 -7
- package/contracts/lib/P2IDHash.sol +5 -5
- package/dist/cjs/p2idConstants.js +2 -2
- package/dist/esm/p2idConstants.d.ts +2 -2
- package/dist/esm/p2idConstants.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Derive deterministic EVM vault addresses from email addresses, social handles an
|
|
|
4
4
|
identities. Addresses can receive funds before the recipient registers or the vault is deployed.
|
|
5
5
|
Claims pay the wallet bound by an accepted identity proof.
|
|
6
6
|
|
|
7
|
+
Learn more about P2ID in the [P2ID protocol specification](https://github.com/pvium/zkid/blob/main/P2ID.md).
|
|
8
|
+
|
|
7
9
|
The package includes identity hashing, address derivation and Solidity sources. For off-chain
|
|
8
10
|
proof verification, use
|
|
9
11
|
[`@pvium/p2id-verifier`](https://www.npmjs.com/package/@pvium/p2id-verifier).
|
package/contracts/P2IDVault.sol
CHANGED
|
@@ -7,75 +7,28 @@ import {IP2IDPolicy} from "./interfaces/IP2IDPolicy.sol";
|
|
|
7
7
|
import {IP2IdVaultFactory} from "./interfaces/IP2IdVaultFactory.sol";
|
|
8
8
|
|
|
9
9
|
/// @title P2IDVault
|
|
10
|
-
/// @notice
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
///
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
///
|
|
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].
|
|
10
|
+
/// @notice Vault implementation for ERC-20 and native-coin deposits associated with an identity.
|
|
11
|
+
/// Uses its immutable factory for policy and default-verifier settings.
|
|
12
|
+
/// @dev Deposits are indexed by (verifier, constraint, token). Cursor-based sweeps visit at most
|
|
13
|
+
/// MAX_SWEEP_PAGE records; explicit-id sweeps iterate the supplied list. Owner caches and
|
|
14
|
+
/// proof floors are per verifier, with an additional cross-default floor for untracked funds.
|
|
15
|
+
/// Direct transfers create no deposit record and have no refund entry point. Token accounting
|
|
16
|
+
/// relies on the token's reported balances and transfer behavior.
|
|
65
17
|
contract P2IDVault is IP2IDVault {
|
|
66
|
-
/// @notice
|
|
18
|
+
/// @notice Maximum fee rate recorded or quoted by this implementation: 100 basis points (1%).
|
|
67
19
|
uint16 public constant MAX_FEE_BPS = 100;
|
|
68
|
-
/// @notice
|
|
20
|
+
/// @notice Token identifier used for the native coin.
|
|
69
21
|
address public constant NATIVE = address(0);
|
|
70
22
|
uint256 private constant BPS = 10_000;
|
|
71
|
-
/// @dev Gas
|
|
72
|
-
///
|
|
23
|
+
/// @dev Gas cap for _query calls. Failed fee/support queries return zero/false; failed revision
|
|
24
|
+
/// queries invalidate cached-owner reads and prevent proof updates.
|
|
73
25
|
uint256 private constant QUERY_GAS = 50_000;
|
|
74
|
-
/// @
|
|
75
|
-
|
|
26
|
+
/// @notice Record limit for cursor-based sweeps; a zero or larger requested limit uses this value.
|
|
27
|
+
uint256 public constant MAX_SWEEP_PAGE = 100;
|
|
28
|
+
/// @dev Maximum accepted issue time ahead of block.timestamp.
|
|
76
29
|
uint64 private constant FUTURE_SLACK = 15 minutes;
|
|
77
30
|
|
|
78
|
-
/// @notice
|
|
31
|
+
/// @notice Factory supplied at construction; source of settings and authorized caller of initialize()/fundFor().
|
|
79
32
|
address public immutable factory;
|
|
80
33
|
bytes32 public nsHash;
|
|
81
34
|
bytes32 public saltCommitment;
|
|
@@ -83,18 +36,17 @@ contract P2IDVault is IP2IDVault {
|
|
|
83
36
|
uint64 public maxRefundWindow;
|
|
84
37
|
bool private _initialized;
|
|
85
38
|
|
|
86
|
-
/// @notice
|
|
39
|
+
/// @notice Cached payout wallet per verifier. Equal-time proofs do not replace an existing wallet.
|
|
87
40
|
mapping(address verifier => address) public owner;
|
|
88
|
-
/// @notice
|
|
41
|
+
/// @notice Stored issue-time floor checked by _apply for this verifier.
|
|
89
42
|
mapping(address verifier => uint64) public latestProofIat;
|
|
90
|
-
/// @notice
|
|
91
|
-
/// is void: the verifier revoked something it trusted then, so the owner must re-prove.
|
|
43
|
+
/// @notice Revision stored with the cached owner; _ownerFresh requires equality with the current revision.
|
|
92
44
|
mapping(address verifier => uint64) public ownerRevision;
|
|
93
45
|
/// @notice Freshness floor for direct transfers: the newest proof presented through the verifier
|
|
94
46
|
/// that was the default at the time. Carries across default changes.
|
|
95
47
|
uint64 public untrackedProofIat;
|
|
96
48
|
|
|
97
|
-
/// @notice
|
|
49
|
+
/// @notice Deposit records, including consumed records, indexed by id.
|
|
98
50
|
Deposit[] public deposits;
|
|
99
51
|
|
|
100
52
|
/// @notice Unconsumed total per (verifier, constraint, token) bucket.
|
|
@@ -105,16 +57,16 @@ contract P2IDVault is IP2IDVault {
|
|
|
105
57
|
mapping(address verifier => mapping(bytes32 constraint => mapping(address token => uint256[]))) private _bucketDeposits;
|
|
106
58
|
/// @notice Index into the bucket's deposit list below which every deposit is consumed.
|
|
107
59
|
mapping(address verifier => mapping(bytes32 constraint => mapping(address token => uint256))) public bucketCursor;
|
|
108
|
-
/// @dev keccak256(constraint, funder) => depositId + 1
|
|
109
|
-
///
|
|
110
|
-
/// same value only puts their own money in the bucket.
|
|
60
|
+
/// @dev keccak256(abi.encode(constraint, funder)) => depositId + 1 for nonzero constraints.
|
|
61
|
+
/// Entries persist after claims and refunds, preventing reuse by the same funder in this vault.
|
|
111
62
|
mapping(bytes32 => uint256) private _constraintDeposit;
|
|
112
63
|
/// @notice Fees accrued and not yet distributed, per verifier they were earned through and token.
|
|
113
64
|
mapping(address verifier => mapping(address token => uint256)) public feesOwed;
|
|
114
65
|
/// @notice Sum of feesOwed per token: held for distribution, never part of a payout.
|
|
115
66
|
mapping(address token => uint256) public feesOwedTotal;
|
|
116
67
|
|
|
117
|
-
|
|
68
|
+
/// @dev 2 while a guarded call runs; any other value (including the 0 a fresh proxy starts with) is idle.
|
|
69
|
+
uint256 private reentrancyLock;
|
|
118
70
|
|
|
119
71
|
error InvalidRefundWindow();
|
|
120
72
|
error VerifierNotApproved(address verifier);
|
|
@@ -123,6 +75,9 @@ contract P2IDVault is IP2IDVault {
|
|
|
123
75
|
error InvalidToken();
|
|
124
76
|
error OwnerNotInitialized();
|
|
125
77
|
error OwnerRevoked();
|
|
78
|
+
error RevisionUnavailable(address verifier);
|
|
79
|
+
error NotSelf();
|
|
80
|
+
error NotOwnerProof(address owner);
|
|
126
81
|
error ProofFromFuture();
|
|
127
82
|
error ProofTooOld();
|
|
128
83
|
error InvalidWallet();
|
|
@@ -142,19 +97,18 @@ contract P2IDVault is IP2IDVault {
|
|
|
142
97
|
error NativeValueMismatch();
|
|
143
98
|
error NativeTransferFailed();
|
|
144
99
|
|
|
145
|
-
/// @dev
|
|
146
|
-
///
|
|
147
|
-
///
|
|
148
|
-
constructor() {
|
|
149
|
-
factory =
|
|
100
|
+
/// @dev This constructor runs only for the implementation; each proxy's storage is set up
|
|
101
|
+
/// separately by the factory calling initialize(). Address derivation uses proxy creation code.
|
|
102
|
+
/// @param _factory Address embedded in this implementation for internal factory references.
|
|
103
|
+
constructor(address _factory) {
|
|
104
|
+
factory = _factory;
|
|
150
105
|
}
|
|
151
106
|
|
|
152
|
-
/// @notice
|
|
153
|
-
///
|
|
154
|
-
/// Deliberately empty, so it works with the 2300-gas stipend of `transfer`/`send`.
|
|
107
|
+
/// @notice Accept native coin without creating a deposit record.
|
|
108
|
+
/// @dev Calls to a vault proxy with empty calldata are handled by the proxy's own receive().
|
|
155
109
|
receive() external payable {}
|
|
156
110
|
|
|
157
|
-
/// @notice
|
|
111
|
+
/// @notice Initialize vault settings once; callable only by the configured factory.
|
|
158
112
|
function initialize(
|
|
159
113
|
bytes32 _nsHash,
|
|
160
114
|
bytes32 _saltCommitment,
|
|
@@ -201,9 +155,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
201
155
|
return _fund(msg.sender, verifier, token, amount, constraint, refundWindow, ref);
|
|
202
156
|
}
|
|
203
157
|
|
|
204
|
-
/// @notice Factory-only:
|
|
205
|
-
///
|
|
206
|
-
/// coin arrives as msg.value.
|
|
158
|
+
/// @notice Factory-only: record a deposit for `funder`. ERC-20 tokens are pulled from the
|
|
159
|
+
/// factory; native coin is supplied as msg.value.
|
|
207
160
|
/// @param ref Opaque application reference emitted in Funded; bytes32(0) for none.
|
|
208
161
|
function fundFor(
|
|
209
162
|
address funder,
|
|
@@ -218,8 +171,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
218
171
|
return _fund(funder, verifier, token, amount, constraint, refundWindow, ref);
|
|
219
172
|
}
|
|
220
173
|
|
|
221
|
-
/// @dev
|
|
222
|
-
///
|
|
174
|
+
/// @dev Receive native msg.value or pull ERC-20 from msg.sender, crediting the reported balance
|
|
175
|
+
/// increase for ERC-20. Record a deposit owned by `funder`, with the fee
|
|
223
176
|
/// rate the policy quotes now (capped) fixed for it.
|
|
224
177
|
function _fund(
|
|
225
178
|
address funder,
|
|
@@ -244,7 +197,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
244
197
|
if (msg.value != amount) revert NativeValueMismatch();
|
|
245
198
|
credited = amount;
|
|
246
199
|
} else {
|
|
247
|
-
if (msg.value != 0) revert NativeValueMismatch(); //
|
|
200
|
+
if (msg.value != 0) revert NativeValueMismatch(); // ERC-20 deposits do not accept msg.value
|
|
248
201
|
if (token.code.length == 0) revert InvalidToken();
|
|
249
202
|
uint256 beforeBalance = _balanceOf(token);
|
|
250
203
|
_callToken(token, abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, address(this), amount));
|
|
@@ -291,17 +244,15 @@ contract P2IDVault is IP2IDVault {
|
|
|
291
244
|
|
|
292
245
|
// ------------------------------------------------------------------ proofs
|
|
293
246
|
|
|
294
|
-
/// @notice
|
|
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.
|
|
247
|
+
/// @notice Verify a proof and update this verifier's owner cache and issue-time floor using _apply.
|
|
297
248
|
function refreshProof(address verifier, bytes calldata proof) external nonReentrant {
|
|
298
249
|
_present(verifier, proof, _noConstraint());
|
|
299
250
|
}
|
|
300
251
|
|
|
301
|
-
/// @notice Present a proof
|
|
252
|
+
/// @notice Present a proof, then sweep `verifier`'s default bucket for `token`.
|
|
302
253
|
/// @param depositCountLimit Number of deposit records to visit from the bucket cursor (not
|
|
303
|
-
/// an amount, not an index);
|
|
304
|
-
///
|
|
254
|
+
/// an amount, not an index); zero or above MAX_SWEEP_PAGE uses MAX_SWEEP_PAGE.
|
|
255
|
+
/// @dev Includes untracked funds only when _paysUntracked(verifier) is true.
|
|
305
256
|
function refreshProofAndSweep(
|
|
306
257
|
address verifier,
|
|
307
258
|
bytes calldata proof,
|
|
@@ -315,8 +266,9 @@ contract P2IDVault is IP2IDVault {
|
|
|
315
266
|
// ------------------------------------------------------------------ claiming
|
|
316
267
|
|
|
317
268
|
/// @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 =
|
|
319
|
-
///
|
|
269
|
+
/// this bucket's list from its cursor; `depositCountLimit` records per call (0 = MAX_SWEEP_PAGE, also the cap).
|
|
270
|
+
/// Includes untracked funds when the verifier is the current default and its owner cache
|
|
271
|
+
/// meets the revision and untrackedProofIat checks.
|
|
320
272
|
function sweep(address verifier, address token, uint256 depositCountLimit)
|
|
321
273
|
external
|
|
322
274
|
nonReentrant
|
|
@@ -325,7 +277,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
325
277
|
return _sweepDefault(verifier, token, depositCountLimit); // checks the owner is set and current
|
|
326
278
|
}
|
|
327
279
|
|
|
328
|
-
/// @notice Sweep only untracked
|
|
280
|
+
/// @notice Sweep only untracked ERC-20 or native funds to
|
|
329
281
|
/// the default verifier's owner. The fee rate is quoted now.
|
|
330
282
|
function sweepUntracked(address token) external nonReentrant returns (uint256 amount) {
|
|
331
283
|
address verifier = defaultVerifier();
|
|
@@ -354,7 +306,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
354
306
|
|
|
355
307
|
/// @notice Sweep the bucket funded under `verifier` and `constraint.commitment`. The verifier
|
|
356
308
|
/// must accept both the identity proof and the constraint evidence; funds go to the
|
|
357
|
-
/// wallet the proof resolves to.
|
|
309
|
+
/// wallet the proof resolves to. This entry point rejects a zero commitment.
|
|
358
310
|
function sweepBucket(
|
|
359
311
|
address verifier,
|
|
360
312
|
IP2IDVerifier.Constraint calldata constraint,
|
|
@@ -388,9 +340,9 @@ contract P2IDVault is IP2IDVault {
|
|
|
388
340
|
|
|
389
341
|
// ------------------------------------------------------------------ fees
|
|
390
342
|
|
|
391
|
-
/// @notice
|
|
392
|
-
///
|
|
393
|
-
///
|
|
343
|
+
/// @notice Call the current policy to distribute accrued fees; callable by any address.
|
|
344
|
+
/// Native fees are sent as msg.value. For ERC-20, approve the accrued amount, reset the
|
|
345
|
+
/// allowance after the call, and deduct the observed balance decrease; revert if it exceeds fees owed.
|
|
394
346
|
function withdrawFees(address verifier, address token) external nonReentrant returns (uint256 amount) {
|
|
395
347
|
uint256 owed = feesOwed[verifier][token];
|
|
396
348
|
if (owed == 0) return 0;
|
|
@@ -420,12 +372,12 @@ contract P2IDVault is IP2IDVault {
|
|
|
420
372
|
return "p2id.vault.v1";
|
|
421
373
|
}
|
|
422
374
|
|
|
423
|
-
/// @notice
|
|
375
|
+
/// @notice Policy address returned by the configured factory.
|
|
424
376
|
function policy() public view returns (address) {
|
|
425
377
|
return IP2IdVaultFactory(factory).policy();
|
|
426
378
|
}
|
|
427
379
|
|
|
428
|
-
/// @notice
|
|
380
|
+
/// @notice Default verifier returned by the configured factory, used by fund() and untracked claims.
|
|
429
381
|
function defaultVerifier() public view returns (address) {
|
|
430
382
|
return IP2IdVaultFactory(factory).defaultVerifier();
|
|
431
383
|
}
|
|
@@ -434,40 +386,48 @@ contract P2IDVault is IP2IDVault {
|
|
|
434
386
|
return deposits.length;
|
|
435
387
|
}
|
|
436
388
|
|
|
437
|
-
/// @notice
|
|
438
|
-
|
|
439
|
-
|
|
389
|
+
/// @notice A page of a bucket's deposit ids, in funding order (consumed ones included; see
|
|
390
|
+
/// `deposits`): up to `limit` ids from `offset`, or all from `offset` when `limit` is 0.
|
|
391
|
+
function bucketDepositIds(address verifier, bytes32 constraint, address token, uint256 offset, uint256 limit)
|
|
392
|
+
external
|
|
393
|
+
view
|
|
394
|
+
returns (uint256[] memory ids)
|
|
395
|
+
{
|
|
396
|
+
uint256[] storage all = _bucketDeposits[verifier][constraint][token];
|
|
397
|
+
if (offset >= all.length) return ids;
|
|
398
|
+
uint256 end = all.length;
|
|
399
|
+
if (limit != 0 && end - offset > limit) end = offset + limit;
|
|
400
|
+
ids = new uint256[](end - offset);
|
|
401
|
+
for (uint256 k = offset; k < end; k++) ids[k - offset] = all[k];
|
|
440
402
|
}
|
|
441
403
|
|
|
442
404
|
function bucketDepositCount(address verifier, bytes32 constraint, address token) external view returns (uint256) {
|
|
443
405
|
return _bucketDeposits[verifier][constraint][token].length;
|
|
444
406
|
}
|
|
445
407
|
|
|
446
|
-
/// @notice
|
|
408
|
+
/// @notice Recorded deposit for a nonzero constraint and funder; the record persists after consumption.
|
|
447
409
|
function constraintDeposit(bytes32 constraint, address funder) external view returns (bool used, uint256 depositId) {
|
|
448
410
|
uint256 stored = _constraintDeposit[keccak256(abi.encode(constraint, funder))];
|
|
449
411
|
return stored == 0 ? (false, 0) : (true, stored - 1);
|
|
450
412
|
}
|
|
451
413
|
|
|
452
|
-
/// @notice
|
|
453
|
-
///
|
|
414
|
+
/// @notice Total default-bucket amount plus untracked funds when _paysUntracked is true.
|
|
415
|
+
/// @dev Does not apply the sweep page limit or validate default-bucket claimability.
|
|
454
416
|
function sweepable(address verifier, address token) external view returns (uint256) {
|
|
455
417
|
uint256 amount = bucketTotal[verifier][bytes32(0)][token];
|
|
456
418
|
if (_paysUntracked(verifier)) amount += _untrackedBalance(token);
|
|
457
419
|
return amount;
|
|
458
420
|
}
|
|
459
421
|
|
|
460
|
-
/// @notice
|
|
422
|
+
/// @notice Reported balance minus tracked deposits and accrued fees, floored at zero; supports native coin.
|
|
461
423
|
function untrackedBalance(address token) external view returns (uint256) {
|
|
462
424
|
return _untrackedBalance(token);
|
|
463
425
|
}
|
|
464
426
|
|
|
465
427
|
// ------------------------------------------------------------------ internals
|
|
466
428
|
|
|
467
|
-
/// @dev
|
|
468
|
-
///
|
|
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.
|
|
429
|
+
/// @dev Check verifier claimability, request verification for saltCommitment, then apply the
|
|
430
|
+
/// returned wallet and issue time. Proxy-verified results enter through acceptOwnerProof.
|
|
471
431
|
function _present(
|
|
472
432
|
address verifier,
|
|
473
433
|
bytes calldata proof,
|
|
@@ -475,15 +435,34 @@ contract P2IDVault is IP2IDVault {
|
|
|
475
435
|
) private onlyClaimable(verifier) returns (address wallet) {
|
|
476
436
|
uint64 iat;
|
|
477
437
|
(wallet, iat) = IP2IDVerifier(verifier).getIdentityWallet(saltCommitment, proof, constraint);
|
|
438
|
+
_apply(verifier, wallet, iat);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// @notice Apply a proxy-verified wallet and issue time; require msg.sender == address(this).
|
|
442
|
+
/// Revert unless wallet is the cached owner afterwards, then return the hook selector.
|
|
443
|
+
/// @dev The self-call check relies on the proxy verifying the result before invoking this hook.
|
|
444
|
+
function acceptOwnerProof(address verifier, address wallet, uint64 iat)
|
|
445
|
+
external
|
|
446
|
+
nonReentrant
|
|
447
|
+
onlyClaimable(verifier)
|
|
448
|
+
returns (bytes4)
|
|
449
|
+
{
|
|
450
|
+
if (msg.sender != address(this)) revert NotSelf();
|
|
451
|
+
_apply(verifier, wallet, iat);
|
|
452
|
+
if (owner[verifier] != wallet) revert NotOwnerProof(owner[verifier]);
|
|
453
|
+
return IP2IDVault.acceptOwnerProof.selector; // compatibility acknowledgement after applying the result
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/// @dev The freshness and revision rules for a verified (wallet, iat) under `verifier`: refuses
|
|
457
|
+
/// proofs older than the newest seen, and makes a newer wallet the owner.
|
|
458
|
+
function _apply(address verifier, address wallet, uint64 iat) private {
|
|
478
459
|
if (wallet == address(0)) revert InvalidWallet();
|
|
479
|
-
//
|
|
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.
|
|
460
|
+
// Bound the issue time before storing it as a freshness floor.
|
|
482
461
|
if (iat > block.timestamp + FUTURE_SLACK) revert ProofFromFuture();
|
|
483
|
-
uint64 rev = _revision(verifier);
|
|
462
|
+
(bool revOk, uint64 rev) = _revision(verifier);
|
|
463
|
+
if (!revOk) revert RevisionUnavailable(verifier);
|
|
484
464
|
bool fresh = ownerRevision[verifier] == rev;
|
|
485
|
-
//
|
|
486
|
-
// retired stay retired, so re-proving takes a proof at least as recent as the last one.
|
|
465
|
+
// Revision changes do not reset this verifier's stored issue-time floor.
|
|
487
466
|
uint64 latest = latestProofIat[verifier];
|
|
488
467
|
if (iat < latest) revert ProofTooOld();
|
|
489
468
|
if (iat > latest || owner[verifier] == address(0)) {
|
|
@@ -502,7 +481,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
502
481
|
// wallet slot of the same token). The owner stays, and constrained paths pay `wallet`.
|
|
503
482
|
}
|
|
504
483
|
|
|
505
|
-
/// @dev
|
|
484
|
+
/// @dev Verify a constrained claim; reject zero commitments at this entry point.
|
|
506
485
|
function _verifyForThisVault(
|
|
507
486
|
address verifier,
|
|
508
487
|
bytes calldata proof,
|
|
@@ -512,14 +491,13 @@ contract P2IDVault is IP2IDVault {
|
|
|
512
491
|
return _present(verifier, proof, constraint);
|
|
513
492
|
}
|
|
514
493
|
|
|
515
|
-
/// @dev
|
|
516
|
-
///
|
|
494
|
+
/// @dev Whether verifier is the current default, its revision matches the cache, and its
|
|
495
|
+
/// stored proof time meets untrackedProofIat. Does not check for a nonzero owner.
|
|
517
496
|
function _paysUntracked(address verifier) private view returns (bool) {
|
|
518
497
|
return verifier == defaultVerifier() && _ownerFresh(verifier) && latestProofIat[verifier] >= untrackedProofIat;
|
|
519
498
|
}
|
|
520
499
|
|
|
521
|
-
/// @dev
|
|
522
|
-
/// must not have revoked anything since the owner was proven.
|
|
500
|
+
/// @dev Require a claimable verifier, nonzero cached owner and matching readable revision.
|
|
523
501
|
function _ownerOf(address verifier) private view onlyClaimable(verifier) returns (address to) {
|
|
524
502
|
to = owner[verifier];
|
|
525
503
|
if (to == address(0)) revert OwnerNotInitialized();
|
|
@@ -528,7 +506,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
528
506
|
|
|
529
507
|
/// @dev Whether the cached owner was proven under the verifier's current revision.
|
|
530
508
|
function _ownerFresh(address verifier) private view returns (bool) {
|
|
531
|
-
|
|
509
|
+
(bool ok, uint64 rev) = _revision(verifier);
|
|
510
|
+
return ok && ownerRevision[verifier] == rev;
|
|
532
511
|
}
|
|
533
512
|
|
|
534
513
|
function _noConstraint() private pure returns (IP2IDVerifier.Constraint memory c) {
|
|
@@ -536,9 +515,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
536
515
|
c.signature = "";
|
|
537
516
|
}
|
|
538
517
|
|
|
539
|
-
/// @dev
|
|
540
|
-
///
|
|
541
|
-
/// factory default.
|
|
518
|
+
/// @dev Consume a page of default-bucket deposits at their stored fee rates. Include untracked
|
|
519
|
+
/// surplus at the current quoted rate when _paysUntracked returns true.
|
|
542
520
|
function _sweepDefault(address verifier, address token, uint256 depositCountLimit)
|
|
543
521
|
private
|
|
544
522
|
returns (uint256 amount, uint256 consumed)
|
|
@@ -569,9 +547,9 @@ contract P2IDVault is IP2IDVault {
|
|
|
569
547
|
uint256[] storage ids = _bucketDeposits[verifier][constraint][token];
|
|
570
548
|
uint256 i = bucketCursor[verifier][constraint][token];
|
|
571
549
|
uint256 end = ids.length;
|
|
572
|
-
//
|
|
573
|
-
|
|
574
|
-
if (
|
|
550
|
+
// Limit records visited, including already-consumed entries, to MAX_SWEEP_PAGE.
|
|
551
|
+
uint256 page = (depositCountLimit == 0 || depositCountLimit > MAX_SWEEP_PAGE) ? MAX_SWEEP_PAGE : depositCountLimit;
|
|
552
|
+
if (end - i > page) end = i + page;
|
|
575
553
|
while (i < end) {
|
|
576
554
|
uint256 id = ids[i];
|
|
577
555
|
Deposit storage deposit = deposits[id];
|
|
@@ -656,10 +634,12 @@ contract P2IDVault is IP2IDVault {
|
|
|
656
634
|
return v > MAX_FEE_BPS ? MAX_FEE_BPS : uint16(v);
|
|
657
635
|
}
|
|
658
636
|
|
|
659
|
-
/// @dev
|
|
660
|
-
function _revision(address verifier) private view returns (uint64) {
|
|
661
|
-
|
|
662
|
-
|
|
637
|
+
/// @dev Query revision() with QUERY_GAS; ok is false on call failure, wrong length or uint64 overflow.
|
|
638
|
+
function _revision(address verifier) private view returns (bool ok, uint64 rev) {
|
|
639
|
+
uint256 v;
|
|
640
|
+
(ok, v) = _query(verifier, abi.encodeCall(IP2IDVerifier.revision, ()));
|
|
641
|
+
if (ok && v > type(uint64).max) ok = false;
|
|
642
|
+
rev = uint64(v);
|
|
663
643
|
}
|
|
664
644
|
|
|
665
645
|
/// @dev Whether `verifier` declares it can satisfy constraints; false if it does not say.
|
|
@@ -678,8 +658,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
678
658
|
|
|
679
659
|
// ------------------------------------------------------------------ tokens
|
|
680
660
|
|
|
681
|
-
/// @dev
|
|
682
|
-
///
|
|
661
|
+
/// @dev Transfer tokens or call the native recipient without an explicit gas limit.
|
|
662
|
+
/// Entry points reaching this helper hold the reentrancy lock.
|
|
683
663
|
function _transfer(address token, address to, uint256 amount) private {
|
|
684
664
|
if (token == NATIVE) {
|
|
685
665
|
(bool ok, ) = payable(to).call{value: amount}("");
|
|
@@ -709,8 +689,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
709
689
|
_;
|
|
710
690
|
}
|
|
711
691
|
|
|
712
|
-
/// @dev
|
|
713
|
-
/// notes), otherwise only while the policy allows the verifier.
|
|
692
|
+
/// @dev Skip policy approval for the current default verifier; check approval for other verifiers.
|
|
714
693
|
modifier onlyClaimable(address verifier) {
|
|
715
694
|
if (verifier != defaultVerifier() && !_policy().isVerifierAllowed(verifier)) revert VerifierNotApproved(verifier);
|
|
716
695
|
_;
|
|
@@ -721,14 +700,14 @@ contract P2IDVault is IP2IDVault {
|
|
|
721
700
|
_;
|
|
722
701
|
}
|
|
723
702
|
|
|
724
|
-
/// @dev
|
|
703
|
+
/// @dev Require a cached owner that passes _ownerOf's claimability and revision checks.
|
|
725
704
|
modifier onlyInitialized(address verifier) {
|
|
726
705
|
_ownerOf(verifier);
|
|
727
706
|
_;
|
|
728
707
|
}
|
|
729
708
|
|
|
730
709
|
modifier nonReentrant() {
|
|
731
|
-
if (reentrancyLock
|
|
710
|
+
if (reentrancyLock == 2) revert Reentrancy();
|
|
732
711
|
reentrancyLock = 2;
|
|
733
712
|
_;
|
|
734
713
|
reentrancyLock = 1;
|