@pvium/p2id-core 0.1.2 → 0.2.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/contracts/P2IDVault.sol +320 -214
- package/contracts/PviumIdentity.sol +29 -36
- package/contracts/PviumP2IDPolicy.sol +4 -7
- package/contracts/PviumP2IDVaultProxy.sol +199 -0
- package/contracts/PviumP2IdVaultFactory.sol +346 -34
- package/contracts/PviumVerifier.sol +15 -21
- package/contracts/PviumZKVerifier.sol +137 -111
- package/contracts/interfaces/IP2IDPolicy.sol +5 -21
- package/contracts/interfaces/IP2IDVault.sol +42 -17
- package/contracts/interfaces/IP2IDVaultProxy.sol +42 -0
- package/contracts/interfaces/IP2IDVerifier.sol +12 -13
- package/contracts/interfaces/IP2IdVaultFactory.sol +41 -7
- package/contracts/interfaces/IPviumIdentity.sol +6 -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 +1 -1
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,
|
|
@@ -170,6 +124,172 @@ contract P2IDVault is IP2IDVault {
|
|
|
170
124
|
maxRefundWindow = _maxRefundWindow;
|
|
171
125
|
}
|
|
172
126
|
|
|
127
|
+
// ------------------------------------------------------------------ alpha authorization
|
|
128
|
+
|
|
129
|
+
/// @notice Alpha authorization nonces already consumed by this vault (caller-chosen, single use).
|
|
130
|
+
/// @dev Preserve this mapping and append later storage after the cache bindings below.
|
|
131
|
+
mapping(uint256 => bool) public alphaNonceUsed;
|
|
132
|
+
mapping(address => bytes32) public ownerVkHash;
|
|
133
|
+
mapping(address => uint256) public ownerAlphaRevision;
|
|
134
|
+
error AlphaAuthorizationRequired();
|
|
135
|
+
error InvalidVkHash();
|
|
136
|
+
error InvalidAlphaCall();
|
|
137
|
+
error AlphaNonceAlreadyUsed(uint256 nonce);
|
|
138
|
+
|
|
139
|
+
function supportsAlphaGuard() external pure returns (bool) { return true; }
|
|
140
|
+
|
|
141
|
+
function executeWithAttestation(bytes calldata action, AlphaAttestation calldata attestation) external validAlphaCall(action) returns (bytes memory) {
|
|
142
|
+
return _dispatchAlphaCall(action, attestation);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function _dispatchAlphaCall(bytes calldata action, AlphaAttestation memory attestation) private returns (bytes memory) {
|
|
146
|
+
if (action.length < 4) revert InvalidAlphaCall();
|
|
147
|
+
bytes4 selector = bytes4(action[:4]);
|
|
148
|
+
if (selector == IP2IDVault.refreshProof.selector) {
|
|
149
|
+
(address verifier, bytes memory proof) = abi.decode(action[4:], (address, bytes));
|
|
150
|
+
refreshProofWithAttestation(verifier, proof, attestation);
|
|
151
|
+
return hex"";
|
|
152
|
+
}
|
|
153
|
+
if (selector == IP2IDVault.refreshProofAndSweep.selector) {
|
|
154
|
+
(address verifier, bytes memory proof, address token, uint256 depositCountLimit) = abi.decode(action[4:], (address, bytes, address, uint256));
|
|
155
|
+
(uint256 amount, uint256 consumed) = refreshProofAndSweepWithAttestation(verifier, proof, token, depositCountLimit, attestation);
|
|
156
|
+
return abi.encode(amount, consumed);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (selector == IP2IDVault.sweepBucket.selector) {
|
|
160
|
+
(address verifier, IP2IDVerifier.Constraint memory constraint, address token, bytes memory proof, uint256 depositCountLimit) = abi.decode(action[4:], (address, IP2IDVerifier.Constraint, address, bytes, uint256));
|
|
161
|
+
(uint256 amount, uint256 consumed) = sweepBucketWithAttestation(verifier, constraint, token, proof, depositCountLimit, attestation);
|
|
162
|
+
return abi.encode(amount, consumed);
|
|
163
|
+
}
|
|
164
|
+
if (selector == IP2IDVault.sweepBucketDeposits.selector) {
|
|
165
|
+
(address verifier, IP2IDVerifier.Constraint memory constraint, address token, uint256[] memory depositIds, bytes memory proof) = abi.decode(action[4:], (address, IP2IDVerifier.Constraint, address, uint256[], bytes));
|
|
166
|
+
return abi.encode(sweepBucketDepositsWithAttestation(verifier, constraint, token, depositIds, proof, attestation));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
revert InvalidAlphaCall();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function refreshProofWithAttestation(address verifier, bytes memory proof, AlphaAttestation memory attestation)
|
|
173
|
+
public nonReentrant
|
|
174
|
+
alphaAttestationRequired(verifier, attestation, abi.encodeCall(IP2IDVault.refreshProof, (verifier, proof)))
|
|
175
|
+
{
|
|
176
|
+
_refreshProof(verifier, proof);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function _refreshProof(address verifier, bytes memory proof) private {
|
|
180
|
+
_present(verifier, proof, _noConstraint());
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function refreshProofAndSweepWithAttestation(address verifier, bytes memory proof, address token, uint256 depositCountLimit, AlphaAttestation memory attestation)
|
|
184
|
+
public nonReentrant
|
|
185
|
+
alphaAttestationRequired(verifier, attestation, abi.encodeCall(IP2IDVault.refreshProofAndSweep, (verifier, proof, token, depositCountLimit)))
|
|
186
|
+
returns (uint256 amount, uint256 consumed)
|
|
187
|
+
{
|
|
188
|
+
return _refreshProofAndSweep(verifier, proof, token, depositCountLimit);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function _refreshProofAndSweep(address verifier, bytes memory proof, address token, uint256 depositCountLimit) private returns (uint256 amount, uint256 consumed) {
|
|
192
|
+
_present(verifier, proof, _noConstraint());
|
|
193
|
+
return _sweepDefault(verifier, token, depositCountLimit);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function _sweep(address verifier, address token, uint256 depositCountLimit) private returns (uint256 amount, uint256 consumed) {
|
|
197
|
+
return _sweepDefault(verifier, token, depositCountLimit); // checks the owner is set and current
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function _sweepUntracked(address token) private returns (uint256 amount) {
|
|
201
|
+
address verifier = defaultVerifier();
|
|
202
|
+
address to = _ownerOf(verifier);
|
|
203
|
+
if (latestProofIat[verifier] < untrackedProofIat) revert ProofTooOld();
|
|
204
|
+
uint256 gross = _untrackedBalance(token);
|
|
205
|
+
uint256 fee = (gross * _quoteFeeBps(verifier, token)) / BPS;
|
|
206
|
+
uint256 charged;
|
|
207
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
208
|
+
emit Swept(verifier, token, amount, charged, to, 0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function _sweepDeposits(address verifier, address token, uint256[] memory depositIds) private onlyInitialized(verifier) returns (uint256 amount) {
|
|
212
|
+
address to = owner[verifier];
|
|
213
|
+
(uint256 gross, uint256 fee) = _consumeIds(verifier, bytes32(0), token, depositIds, to);
|
|
214
|
+
uint256 charged;
|
|
215
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
216
|
+
emit Swept(verifier, token, amount, charged, to, depositIds.length);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function sweepBucketWithAttestation(address verifier, IP2IDVerifier.Constraint memory constraint, address token, bytes memory proof, uint256 depositCountLimit, AlphaAttestation memory attestation)
|
|
220
|
+
public nonReentrant
|
|
221
|
+
alphaAttestationRequired(verifier, attestation, abi.encodeCall(IP2IDVault.sweepBucket, (verifier, constraint, token, proof, depositCountLimit)))
|
|
222
|
+
returns (uint256 amount, uint256 consumed)
|
|
223
|
+
{
|
|
224
|
+
return _sweepBucket(verifier, constraint, token, proof, depositCountLimit);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function _sweepBucket(address verifier, IP2IDVerifier.Constraint memory constraint, address token, bytes memory proof, uint256 depositCountLimit) private returns (uint256 amount, uint256 consumed) {
|
|
228
|
+
address to = _verifyForThisVault(verifier, proof, constraint);
|
|
229
|
+
uint256 gross;
|
|
230
|
+
uint256 fee;
|
|
231
|
+
(gross, fee, consumed) = _consumeFromCursor(verifier, constraint.commitment, token, depositCountLimit, to);
|
|
232
|
+
uint256 charged;
|
|
233
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
234
|
+
emit SweptBucket(verifier, constraint.commitment, token, amount, charged, to, consumed);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function sweepBucketDepositsWithAttestation(address verifier, IP2IDVerifier.Constraint memory constraint, address token, uint256[] memory depositIds, bytes memory proof, AlphaAttestation memory attestation)
|
|
238
|
+
public nonReentrant
|
|
239
|
+
alphaAttestationRequired(verifier, attestation, abi.encodeCall(IP2IDVault.sweepBucketDeposits, (verifier, constraint, token, depositIds, proof)))
|
|
240
|
+
returns (uint256 amount)
|
|
241
|
+
{
|
|
242
|
+
return _sweepBucketDeposits(verifier, constraint, token, depositIds, proof);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function _sweepBucketDeposits(address verifier, IP2IDVerifier.Constraint memory constraint, address token, uint256[] memory depositIds, bytes memory proof) private returns (uint256 amount) {
|
|
246
|
+
address to = _verifyForThisVault(verifier, proof, constraint);
|
|
247
|
+
(uint256 gross, uint256 fee) = _consumeIds(verifier, constraint.commitment, token, depositIds, to);
|
|
248
|
+
uint256 charged;
|
|
249
|
+
(amount, charged) = _payout(verifier, token, to, gross, fee);
|
|
250
|
+
emit SweptBucket(verifier, constraint.commitment, token, amount, charged, to, depositIds.length);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function _withdrawFees(address verifier, address token) private returns (uint256 amount) {
|
|
254
|
+
uint256 owed = feesOwed[verifier][token];
|
|
255
|
+
if (owed == 0) return 0;
|
|
256
|
+
address pol = policy();
|
|
257
|
+
if (token == NATIVE) {
|
|
258
|
+
// Native coin cannot be pulled: it is sent along with the call, exactly the amount owed.
|
|
259
|
+
IP2IDPolicy(pol).distributeFee{value: owed}(verifier, token, owed);
|
|
260
|
+
amount = owed;
|
|
261
|
+
} else {
|
|
262
|
+
uint256 before = _balanceOf(token);
|
|
263
|
+
_callToken(token, abi.encodeWithSignature("approve(address,uint256)", pol, owed));
|
|
264
|
+
IP2IDPolicy(pol).distributeFee(verifier, token, owed);
|
|
265
|
+
_callToken(token, abi.encodeWithSignature("approve(address,uint256)", pol, 0));
|
|
266
|
+
uint256 afterBalance = _balanceOf(token);
|
|
267
|
+
amount = before > afterBalance ? before - afterBalance : 0;
|
|
268
|
+
if (amount > owed) revert FeeOverdrawn();
|
|
269
|
+
}
|
|
270
|
+
feesOwed[verifier][token] = owed - amount;
|
|
271
|
+
feesOwedTotal[token] -= amount;
|
|
272
|
+
emit FeesDistributed(verifier, token, amount, pol);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
modifier validAlphaCall(bytes calldata action) {
|
|
276
|
+
if (action.length < 4) revert InvalidAlphaCall();
|
|
277
|
+
bytes4 selector = bytes4(action[:4]);
|
|
278
|
+
if (!(selector == IP2IDVault.refreshProof.selector || selector == IP2IDVault.refreshProofAndSweep.selector || selector == IP2IDVault.sweepBucket.selector || selector == IP2IDVault.sweepBucketDeposits.selector)) revert InvalidAlphaCall();
|
|
279
|
+
_;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// @dev Empty signatures are allowed only after alpha. Bind approval to the entire ordinary call.
|
|
283
|
+
modifier alphaAttestationRequired(address verifier, AlphaAttestation memory attestation, bytes memory action) {
|
|
284
|
+
if (IP2IdVaultFactory(factory).isAlpha(IP2IDVerifier(verifier).vkHash())) {
|
|
285
|
+
if (attestation.signature.length == 0) revert AlphaAuthorizationRequired();
|
|
286
|
+
if (alphaNonceUsed[attestation.nonce]) revert AlphaNonceAlreadyUsed(attestation.nonce);
|
|
287
|
+
IP2IdVaultFactory(factory).verifyAlphaAuthorization(address(this), msg.sender, keccak256(action), attestation.nonce, attestation.deadline, attestation.signature);
|
|
288
|
+
alphaNonceUsed[attestation.nonce] = true;
|
|
289
|
+
}
|
|
290
|
+
_;
|
|
291
|
+
}
|
|
292
|
+
|
|
173
293
|
// ------------------------------------------------------------------ funding
|
|
174
294
|
|
|
175
295
|
/// @notice Fund under the factory's default verifier.
|
|
@@ -201,9 +321,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
201
321
|
return _fund(msg.sender, verifier, token, amount, constraint, refundWindow, ref);
|
|
202
322
|
}
|
|
203
323
|
|
|
204
|
-
/// @notice Factory-only:
|
|
205
|
-
///
|
|
206
|
-
/// coin arrives as msg.value.
|
|
324
|
+
/// @notice Factory-only: record a deposit for `funder`. ERC-20 tokens are pulled from the
|
|
325
|
+
/// factory; native coin is supplied as msg.value.
|
|
207
326
|
/// @param ref Opaque application reference emitted in Funded; bytes32(0) for none.
|
|
208
327
|
function fundFor(
|
|
209
328
|
address funder,
|
|
@@ -218,8 +337,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
218
337
|
return _fund(funder, verifier, token, amount, constraint, refundWindow, ref);
|
|
219
338
|
}
|
|
220
339
|
|
|
221
|
-
/// @dev
|
|
222
|
-
///
|
|
340
|
+
/// @dev Receive native msg.value or pull ERC-20 from msg.sender, crediting the reported balance
|
|
341
|
+
/// increase for ERC-20. Record a deposit owned by `funder`, with the fee
|
|
223
342
|
/// rate the policy quotes now (capped) fixed for it.
|
|
224
343
|
function _fund(
|
|
225
344
|
address funder,
|
|
@@ -244,7 +363,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
244
363
|
if (msg.value != amount) revert NativeValueMismatch();
|
|
245
364
|
credited = amount;
|
|
246
365
|
} else {
|
|
247
|
-
if (msg.value != 0) revert NativeValueMismatch(); //
|
|
366
|
+
if (msg.value != 0) revert NativeValueMismatch(); // ERC-20 deposits do not accept msg.value
|
|
248
367
|
if (token.code.length == 0) revert InvalidToken();
|
|
249
368
|
uint256 beforeBalance = _balanceOf(token);
|
|
250
369
|
_callToken(token, abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, address(this), amount));
|
|
@@ -291,126 +410,76 @@ contract P2IDVault is IP2IDVault {
|
|
|
291
410
|
|
|
292
411
|
// ------------------------------------------------------------------ proofs
|
|
293
412
|
|
|
294
|
-
/// @notice
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
function refreshProof(address verifier, bytes calldata proof) external nonReentrant {
|
|
298
|
-
_present(verifier, proof, _noConstraint());
|
|
413
|
+
/// @notice Verify a proof and update this verifier's owner cache and issue-time floor using _apply.
|
|
414
|
+
function refreshProof(address verifier, bytes memory proof) external {
|
|
415
|
+
refreshProofWithAttestation(verifier, proof, AlphaAttestation(0, 0, hex""));
|
|
299
416
|
}
|
|
300
417
|
|
|
301
|
-
/// @notice Present a proof
|
|
418
|
+
/// @notice Present a proof, then sweep `verifier`'s default bucket for `token`.
|
|
302
419
|
/// @param depositCountLimit Number of deposit records to visit from the bucket cursor (not
|
|
303
|
-
/// an amount, not an index);
|
|
304
|
-
///
|
|
420
|
+
/// an amount, not an index); zero or above MAX_SWEEP_PAGE uses MAX_SWEEP_PAGE.
|
|
421
|
+
/// @dev Includes untracked funds only when _paysUntracked(verifier) is true.
|
|
305
422
|
function refreshProofAndSweep(
|
|
306
423
|
address verifier,
|
|
307
|
-
bytes
|
|
424
|
+
bytes memory proof,
|
|
308
425
|
address token,
|
|
309
426
|
uint256 depositCountLimit
|
|
310
|
-
) external
|
|
311
|
-
|
|
312
|
-
return _sweepDefault(verifier, token, depositCountLimit);
|
|
427
|
+
) external returns (uint256 amount, uint256 consumed) {
|
|
428
|
+
return refreshProofAndSweepWithAttestation(verifier, proof, token, depositCountLimit, AlphaAttestation(0, 0, hex""));
|
|
313
429
|
}
|
|
314
430
|
|
|
315
431
|
// ------------------------------------------------------------------ claiming
|
|
316
432
|
|
|
317
433
|
/// @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
|
-
///
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
returns (uint256 amount, uint256 consumed)
|
|
324
|
-
{
|
|
325
|
-
return _sweepDefault(verifier, token, depositCountLimit); // checks the owner is set and current
|
|
434
|
+
/// this bucket's list from its cursor; `depositCountLimit` records per call (0 = MAX_SWEEP_PAGE, also the cap).
|
|
435
|
+
/// Includes untracked funds when the verifier is the current default and its owner cache
|
|
436
|
+
/// meets the revision and untrackedProofIat checks.
|
|
437
|
+
function sweep(address verifier, address token, uint256 depositCountLimit) external nonReentrant returns (uint256 amount, uint256 consumed) {
|
|
438
|
+
return _sweep(verifier, token, depositCountLimit);
|
|
326
439
|
}
|
|
327
440
|
|
|
328
|
-
/// @notice Sweep only untracked
|
|
441
|
+
/// @notice Sweep only untracked ERC-20 or native funds to
|
|
329
442
|
/// the default verifier's owner. The fee rate is quoted now.
|
|
330
443
|
function sweepUntracked(address token) external nonReentrant returns (uint256 amount) {
|
|
331
|
-
|
|
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);
|
|
444
|
+
return _sweepUntracked(token);
|
|
339
445
|
}
|
|
340
446
|
|
|
341
447
|
/// @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[]
|
|
343
|
-
|
|
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);
|
|
448
|
+
function sweepDeposits(address verifier, address token, uint256[] memory depositIds) external nonReentrant returns (uint256 amount) {
|
|
449
|
+
return _sweepDeposits(verifier, token, depositIds);
|
|
353
450
|
}
|
|
354
451
|
|
|
355
452
|
/// @notice Sweep the bucket funded under `verifier` and `constraint.commitment`. The verifier
|
|
356
453
|
/// must accept both the identity proof and the constraint evidence; funds go to the
|
|
357
|
-
/// wallet the proof resolves to.
|
|
454
|
+
/// wallet the proof resolves to. This entry point rejects a zero commitment.
|
|
358
455
|
function sweepBucket(
|
|
359
456
|
address verifier,
|
|
360
|
-
IP2IDVerifier.Constraint
|
|
457
|
+
IP2IDVerifier.Constraint memory constraint,
|
|
361
458
|
address token,
|
|
362
|
-
bytes
|
|
459
|
+
bytes memory proof,
|
|
363
460
|
uint256 depositCountLimit
|
|
364
|
-
) external
|
|
365
|
-
|
|
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);
|
|
461
|
+
) external returns (uint256 amount, uint256 consumed) {
|
|
462
|
+
return sweepBucketWithAttestation(verifier, constraint, token, proof, depositCountLimit, AlphaAttestation(0, 0, hex""));
|
|
372
463
|
}
|
|
373
464
|
|
|
374
465
|
/// @notice Sweep specific deposits of a constrained bucket by id.
|
|
375
466
|
function sweepBucketDeposits(
|
|
376
467
|
address verifier,
|
|
377
|
-
IP2IDVerifier.Constraint
|
|
468
|
+
IP2IDVerifier.Constraint memory constraint,
|
|
378
469
|
address token,
|
|
379
|
-
uint256[]
|
|
380
|
-
bytes
|
|
381
|
-
) external
|
|
382
|
-
|
|
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);
|
|
470
|
+
uint256[] memory depositIds,
|
|
471
|
+
bytes memory proof
|
|
472
|
+
) external returns (uint256 amount) {
|
|
473
|
+
return sweepBucketDepositsWithAttestation(verifier, constraint, token, depositIds, proof, AlphaAttestation(0, 0, hex""));
|
|
387
474
|
}
|
|
388
475
|
|
|
389
476
|
// ------------------------------------------------------------------ fees
|
|
390
477
|
|
|
391
|
-
/// @notice
|
|
392
|
-
///
|
|
393
|
-
///
|
|
478
|
+
/// @notice Call the current policy to distribute accrued fees; callable by any address.
|
|
479
|
+
/// Native fees are sent as msg.value. For ERC-20, approve the accrued amount, reset the
|
|
480
|
+
/// allowance after the call, and deduct the observed balance decrease; revert if it exceeds fees owed.
|
|
394
481
|
function withdrawFees(address verifier, address token) external nonReentrant returns (uint256 amount) {
|
|
395
|
-
|
|
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);
|
|
482
|
+
return _withdrawFees(verifier, token);
|
|
414
483
|
}
|
|
415
484
|
|
|
416
485
|
// ------------------------------------------------------------------ views
|
|
@@ -420,12 +489,12 @@ contract P2IDVault is IP2IDVault {
|
|
|
420
489
|
return "p2id.vault.v1";
|
|
421
490
|
}
|
|
422
491
|
|
|
423
|
-
/// @notice
|
|
492
|
+
/// @notice Policy address returned by the configured factory.
|
|
424
493
|
function policy() public view returns (address) {
|
|
425
494
|
return IP2IdVaultFactory(factory).policy();
|
|
426
495
|
}
|
|
427
496
|
|
|
428
|
-
/// @notice
|
|
497
|
+
/// @notice Default verifier returned by the configured factory, used by fund() and untracked claims.
|
|
429
498
|
function defaultVerifier() public view returns (address) {
|
|
430
499
|
return IP2IdVaultFactory(factory).defaultVerifier();
|
|
431
500
|
}
|
|
@@ -434,56 +503,83 @@ contract P2IDVault is IP2IDVault {
|
|
|
434
503
|
return deposits.length;
|
|
435
504
|
}
|
|
436
505
|
|
|
437
|
-
/// @notice
|
|
438
|
-
|
|
439
|
-
|
|
506
|
+
/// @notice A page of a bucket's deposit ids, in funding order (consumed ones included; see
|
|
507
|
+
/// `deposits`): up to `limit` ids from `offset`, or all from `offset` when `limit` is 0.
|
|
508
|
+
function bucketDepositIds(address verifier, bytes32 constraint, address token, uint256 offset, uint256 limit)
|
|
509
|
+
external
|
|
510
|
+
view
|
|
511
|
+
returns (uint256[] memory ids)
|
|
512
|
+
{
|
|
513
|
+
uint256[] storage all = _bucketDeposits[verifier][constraint][token];
|
|
514
|
+
if (offset >= all.length) return ids;
|
|
515
|
+
uint256 end = all.length;
|
|
516
|
+
if (limit != 0 && end - offset > limit) end = offset + limit;
|
|
517
|
+
ids = new uint256[](end - offset);
|
|
518
|
+
for (uint256 k = offset; k < end; k++) ids[k - offset] = all[k];
|
|
440
519
|
}
|
|
441
520
|
|
|
442
521
|
function bucketDepositCount(address verifier, bytes32 constraint, address token) external view returns (uint256) {
|
|
443
522
|
return _bucketDeposits[verifier][constraint][token].length;
|
|
444
523
|
}
|
|
445
524
|
|
|
446
|
-
/// @notice
|
|
525
|
+
/// @notice Recorded deposit for a nonzero constraint and funder; the record persists after consumption.
|
|
447
526
|
function constraintDeposit(bytes32 constraint, address funder) external view returns (bool used, uint256 depositId) {
|
|
448
527
|
uint256 stored = _constraintDeposit[keccak256(abi.encode(constraint, funder))];
|
|
449
528
|
return stored == 0 ? (false, 0) : (true, stored - 1);
|
|
450
529
|
}
|
|
451
530
|
|
|
452
|
-
/// @notice
|
|
453
|
-
///
|
|
531
|
+
/// @notice Total default-bucket amount plus untracked funds when _paysUntracked is true.
|
|
532
|
+
/// @dev Does not apply the sweep page limit or validate default-bucket claimability.
|
|
454
533
|
function sweepable(address verifier, address token) external view returns (uint256) {
|
|
455
534
|
uint256 amount = bucketTotal[verifier][bytes32(0)][token];
|
|
456
535
|
if (_paysUntracked(verifier)) amount += _untrackedBalance(token);
|
|
457
536
|
return amount;
|
|
458
537
|
}
|
|
459
538
|
|
|
460
|
-
/// @notice
|
|
539
|
+
/// @notice Reported balance minus tracked deposits and accrued fees, floored at zero; supports native coin.
|
|
461
540
|
function untrackedBalance(address token) external view returns (uint256) {
|
|
462
541
|
return _untrackedBalance(token);
|
|
463
542
|
}
|
|
464
543
|
|
|
465
544
|
// ------------------------------------------------------------------ internals
|
|
466
545
|
|
|
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.
|
|
546
|
+
/// @dev Check verifier claimability, request verification for saltCommitment, then apply the
|
|
547
|
+
/// returned wallet and issue time. Proxy-verified results enter through acceptOwnerProof.
|
|
471
548
|
function _present(
|
|
472
549
|
address verifier,
|
|
473
|
-
bytes
|
|
550
|
+
bytes memory proof,
|
|
474
551
|
IP2IDVerifier.Constraint memory constraint
|
|
475
552
|
) private onlyClaimable(verifier) returns (address wallet) {
|
|
476
553
|
uint64 iat;
|
|
477
554
|
(wallet, iat) = IP2IDVerifier(verifier).getIdentityWallet(saltCommitment, proof, constraint);
|
|
555
|
+
_apply(verifier, wallet, iat);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/// @notice Apply a proxy-verified wallet and issue time; require msg.sender == address(this).
|
|
559
|
+
/// Revert unless wallet is the cached owner afterwards, then return the hook selector.
|
|
560
|
+
/// @dev The self-call check relies on the proxy verifying the result before invoking this hook.
|
|
561
|
+
function acceptOwnerProof(address verifier, address wallet, uint64 iat)
|
|
562
|
+
external
|
|
563
|
+
nonReentrant
|
|
564
|
+
onlyClaimable(verifier)
|
|
565
|
+
returns (bytes4)
|
|
566
|
+
{
|
|
567
|
+
if (msg.sender != address(this)) revert NotSelf();
|
|
568
|
+
_apply(verifier, wallet, iat);
|
|
569
|
+
if (owner[verifier] != wallet) revert NotOwnerProof(owner[verifier]);
|
|
570
|
+
return IP2IDVault.acceptOwnerProof.selector; // compatibility acknowledgement after applying the result
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/// @dev The freshness and revision rules for a verified (wallet, iat) under `verifier`: refuses
|
|
574
|
+
/// proofs older than the newest seen, and makes a newer wallet the owner.
|
|
575
|
+
function _apply(address verifier, address wallet, uint64 iat) private {
|
|
478
576
|
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.
|
|
577
|
+
// Bound the issue time before storing it as a freshness floor.
|
|
482
578
|
if (iat > block.timestamp + FUTURE_SLACK) revert ProofFromFuture();
|
|
483
|
-
uint64 rev = _revision(verifier);
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
//
|
|
579
|
+
(bool revOk, uint64 rev) = _revision(verifier);
|
|
580
|
+
if (!revOk) revert RevisionUnavailable(verifier);
|
|
581
|
+
bool fresh = _ownerFresh(verifier);
|
|
582
|
+
// Revision changes do not reset this verifier's stored issue-time floor.
|
|
487
583
|
uint64 latest = latestProofIat[verifier];
|
|
488
584
|
if (iat < latest) revert ProofTooOld();
|
|
489
585
|
if (iat > latest || owner[verifier] == address(0)) {
|
|
@@ -497,29 +593,34 @@ contract P2IDVault is IP2IDVault {
|
|
|
497
593
|
ownerRevision[verifier] = rev;
|
|
498
594
|
emit OwnerRefreshed(verifier, wallet, iat);
|
|
499
595
|
}
|
|
596
|
+
if (wallet == owner[verifier] && iat == latestProofIat[verifier]) {
|
|
597
|
+
bytes32 key = IP2IDVerifier(verifier).vkHash();
|
|
598
|
+
if (key == bytes32(0)) revert InvalidVkHash();
|
|
599
|
+
ownerVkHash[verifier] = key;
|
|
600
|
+
ownerAlphaRevision[verifier] = IP2IdVaultFactory(factory).alphaRevision(key);
|
|
601
|
+
}
|
|
500
602
|
if (iat > untrackedProofIat && verifier == defaultVerifier()) untrackedProofIat = iat;
|
|
501
603
|
// Otherwise iat == latest with the owner set: a same-age proof (a replayed copy, or another
|
|
502
604
|
// wallet slot of the same token). The owner stays, and constrained paths pay `wallet`.
|
|
503
605
|
}
|
|
504
606
|
|
|
505
|
-
/// @dev
|
|
607
|
+
/// @dev Verify a constrained claim; reject zero commitments at this entry point.
|
|
506
608
|
function _verifyForThisVault(
|
|
507
609
|
address verifier,
|
|
508
|
-
bytes
|
|
509
|
-
IP2IDVerifier.Constraint
|
|
610
|
+
bytes memory proof,
|
|
611
|
+
IP2IDVerifier.Constraint memory constraint
|
|
510
612
|
) private returns (address wallet) {
|
|
511
613
|
if (constraint.commitment == bytes32(0)) revert ConstraintRequired();
|
|
512
614
|
return _present(verifier, proof, constraint);
|
|
513
615
|
}
|
|
514
616
|
|
|
515
|
-
/// @dev
|
|
516
|
-
///
|
|
617
|
+
/// @dev Whether verifier is the current default, its revision matches the cache, and its
|
|
618
|
+
/// stored proof time meets untrackedProofIat. Does not check for a nonzero owner.
|
|
517
619
|
function _paysUntracked(address verifier) private view returns (bool) {
|
|
518
620
|
return verifier == defaultVerifier() && _ownerFresh(verifier) && latestProofIat[verifier] >= untrackedProofIat;
|
|
519
621
|
}
|
|
520
622
|
|
|
521
|
-
/// @dev
|
|
522
|
-
/// must not have revoked anything since the owner was proven.
|
|
623
|
+
/// @dev Require a claimable verifier, nonzero cached owner and matching readable revision.
|
|
523
624
|
function _ownerOf(address verifier) private view onlyClaimable(verifier) returns (address to) {
|
|
524
625
|
to = owner[verifier];
|
|
525
626
|
if (to == address(0)) revert OwnerNotInitialized();
|
|
@@ -528,7 +629,12 @@ contract P2IDVault is IP2IDVault {
|
|
|
528
629
|
|
|
529
630
|
/// @dev Whether the cached owner was proven under the verifier's current revision.
|
|
530
631
|
function _ownerFresh(address verifier) private view returns (bool) {
|
|
531
|
-
|
|
632
|
+
(bool ok, uint64 rev) = _revision(verifier);
|
|
633
|
+
if (!ok || ownerRevision[verifier] != rev) return false;
|
|
634
|
+
try IP2IDVerifier(verifier).vkHash() returns (bytes32 key) {
|
|
635
|
+
return key != bytes32(0) && ownerVkHash[verifier] == key
|
|
636
|
+
&& ownerAlphaRevision[verifier] == IP2IdVaultFactory(factory).alphaRevision(key);
|
|
637
|
+
} catch { return false; }
|
|
532
638
|
}
|
|
533
639
|
|
|
534
640
|
function _noConstraint() private pure returns (IP2IDVerifier.Constraint memory c) {
|
|
@@ -536,9 +642,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
536
642
|
c.signature = "";
|
|
537
643
|
}
|
|
538
644
|
|
|
539
|
-
/// @dev
|
|
540
|
-
///
|
|
541
|
-
/// factory default.
|
|
645
|
+
/// @dev Consume a page of default-bucket deposits at their stored fee rates. Include untracked
|
|
646
|
+
/// surplus at the current quoted rate when _paysUntracked returns true.
|
|
542
647
|
function _sweepDefault(address verifier, address token, uint256 depositCountLimit)
|
|
543
648
|
private
|
|
544
649
|
returns (uint256 amount, uint256 consumed)
|
|
@@ -569,9 +674,9 @@ contract P2IDVault is IP2IDVault {
|
|
|
569
674
|
uint256[] storage ids = _bucketDeposits[verifier][constraint][token];
|
|
570
675
|
uint256 i = bucketCursor[verifier][constraint][token];
|
|
571
676
|
uint256 end = ids.length;
|
|
572
|
-
//
|
|
573
|
-
|
|
574
|
-
if (
|
|
677
|
+
// Limit records visited, including already-consumed entries, to MAX_SWEEP_PAGE.
|
|
678
|
+
uint256 page = (depositCountLimit == 0 || depositCountLimit > MAX_SWEEP_PAGE) ? MAX_SWEEP_PAGE : depositCountLimit;
|
|
679
|
+
if (end - i > page) end = i + page;
|
|
575
680
|
while (i < end) {
|
|
576
681
|
uint256 id = ids[i];
|
|
577
682
|
Deposit storage deposit = deposits[id];
|
|
@@ -590,7 +695,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
590
695
|
address verifier,
|
|
591
696
|
bytes32 constraint,
|
|
592
697
|
address token,
|
|
593
|
-
uint256[]
|
|
698
|
+
uint256[] memory depositIds,
|
|
594
699
|
address to
|
|
595
700
|
) private returns (uint256 amount, uint256 fee) {
|
|
596
701
|
for (uint256 k = 0; k < depositIds.length; k++) {
|
|
@@ -656,10 +761,12 @@ contract P2IDVault is IP2IDVault {
|
|
|
656
761
|
return v > MAX_FEE_BPS ? MAX_FEE_BPS : uint16(v);
|
|
657
762
|
}
|
|
658
763
|
|
|
659
|
-
/// @dev
|
|
660
|
-
function _revision(address verifier) private view returns (uint64) {
|
|
661
|
-
|
|
662
|
-
|
|
764
|
+
/// @dev Query revision() with QUERY_GAS; ok is false on call failure, wrong length or uint64 overflow.
|
|
765
|
+
function _revision(address verifier) private view returns (bool ok, uint64 rev) {
|
|
766
|
+
uint256 v;
|
|
767
|
+
(ok, v) = _query(verifier, abi.encodeCall(IP2IDVerifier.revision, ()));
|
|
768
|
+
if (ok && v > type(uint64).max) ok = false;
|
|
769
|
+
rev = uint64(v);
|
|
663
770
|
}
|
|
664
771
|
|
|
665
772
|
/// @dev Whether `verifier` declares it can satisfy constraints; false if it does not say.
|
|
@@ -678,8 +785,8 @@ contract P2IDVault is IP2IDVault {
|
|
|
678
785
|
|
|
679
786
|
// ------------------------------------------------------------------ tokens
|
|
680
787
|
|
|
681
|
-
/// @dev
|
|
682
|
-
///
|
|
788
|
+
/// @dev Transfer tokens or call the native recipient without an explicit gas limit.
|
|
789
|
+
/// Entry points reaching this helper hold the reentrancy lock.
|
|
683
790
|
function _transfer(address token, address to, uint256 amount) private {
|
|
684
791
|
if (token == NATIVE) {
|
|
685
792
|
(bool ok, ) = payable(to).call{value: amount}("");
|
|
@@ -709,8 +816,7 @@ contract P2IDVault is IP2IDVault {
|
|
|
709
816
|
_;
|
|
710
817
|
}
|
|
711
818
|
|
|
712
|
-
/// @dev
|
|
713
|
-
/// notes), otherwise only while the policy allows the verifier.
|
|
819
|
+
/// @dev Skip policy approval for the current default verifier; check approval for other verifiers.
|
|
714
820
|
modifier onlyClaimable(address verifier) {
|
|
715
821
|
if (verifier != defaultVerifier() && !_policy().isVerifierAllowed(verifier)) revert VerifierNotApproved(verifier);
|
|
716
822
|
_;
|
|
@@ -721,14 +827,14 @@ contract P2IDVault is IP2IDVault {
|
|
|
721
827
|
_;
|
|
722
828
|
}
|
|
723
829
|
|
|
724
|
-
/// @dev
|
|
830
|
+
/// @dev Require a cached owner that passes _ownerOf's claimability and revision checks.
|
|
725
831
|
modifier onlyInitialized(address verifier) {
|
|
726
832
|
_ownerOf(verifier);
|
|
727
833
|
_;
|
|
728
834
|
}
|
|
729
835
|
|
|
730
836
|
modifier nonReentrant() {
|
|
731
|
-
if (reentrancyLock
|
|
837
|
+
if (reentrancyLock == 2) revert Reentrancy();
|
|
732
838
|
reentrancyLock = 2;
|
|
733
839
|
_;
|
|
734
840
|
reentrancyLock = 1;
|