-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIdentityRegistry.sol
More file actions
153 lines (133 loc) · 6.25 KB
/
Copy pathIdentityRegistry.sol
File metadata and controls
153 lines (133 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IIdentityRegistry} from "./interfaces/IIdentityRegistry.sol";
/// @title IdentityRegistry
/// @notice Mirror of Cleanverse CVI, written by a permissioned off-chain relayer that
/// polls `query_apass`.
///
/// This is NOT what Covenant runs on Monad. CVI turned out to have a real
/// on-chain read surface, so production uses `CleanverseIdentityRegistry`,
/// which reads it directly and needs no oracle at all. This contract survives
/// for the two cases where that isn't possible: local devnets and any chain
/// where CVI is not deployed. Both implement `IIdentityRegistry`, so nothing
/// downstream changes.
///
/// Trust model, where it is used: a single-writer oracle, exactly as honest as
/// the relayer key. `verifiedUntil` makes staleness self-expiring with no
/// keeper required.
contract IdentityRegistry is Ownable, IIdentityRegistry {
struct Identity {
bool verified; // active + unexpired + not sanctioned, per relayer
uint8 tier; // Cleanverse-assigned A-Pass tier (KYC depth)
uint8 subTier; // integrator-set band (0-99) — Covenant's pricing axis
uint16 jurisdiction; // A-Pass "group", mapped to a numeric jurisdiction code
uint64 verifiedUntil; // min(A-Pass expirationTime, now + relayer TTL)
uint64 updatedAt;
}
/// @notice Sole authorized writer of identity fields (the CVI-mirroring relayer).
address public relayer;
mapping(address => Identity) private _identity;
event IdentityUpserted(
address indexed subject, bool verified, uint8 tier, uint8 subTier, uint16 jurisdiction, uint64 verifiedUntil
);
event RelayerChanged(address indexed relayer);
/// Cleanverse documents subTier as an integrator-set band in 0–99.
uint8 internal constant MAX_SUBTIER = 99;
error NotRelayer();
error ZeroRelayer();
modifier onlyRelayer() {
if (msg.sender != relayer) revert NotRelayer();
_;
}
/// Setting the relayer to the zero address would leave nobody able to write an
/// identity — `onlyRelayer` compares against it, and no transaction has a zero
/// sender — so the registry would be permanently frozen with the owner unable to
/// tell from any read that it had happened. Slither flagged both this and
/// `setRelayer` (docs/STATIC-ANALYSIS.md).
constructor(address initialRelayer) Ownable(msg.sender) {
if (initialRelayer == address(0)) revert ZeroRelayer();
relayer = initialRelayer;
emit RelayerChanged(initialRelayer);
}
// ---------------------------------------------------------------------
// Writes
// ---------------------------------------------------------------------
/// @notice Relayer-only mirror write, called after a fresh `query_apass` read.
/// Idempotent: writing the same values again is a harmless no-op on-chain.
function upsertIdentity(
address subject,
bool verified,
uint8 subjectTier,
uint8 subjectSubTier,
uint16 subjectJurisdiction,
uint64 verifiedUntil
) external onlyRelayer {
_write(subject, verified, subjectTier, subjectSubTier, subjectJurisdiction, verifiedUntil);
}
/// @notice Owner-only override so demo identities can be seeded deterministically
/// with zero live Cleanverse calls. This is the insurance policy for the
/// demo: the climax never depends on a cold sandbox API.
function adminSetIdentity(
address subject,
bool verified,
uint8 subjectTier,
uint8 subjectSubTier,
uint16 subjectJurisdiction,
uint64 verifiedUntil
) external onlyOwner {
_write(subject, verified, subjectTier, subjectSubTier, subjectJurisdiction, verifiedUntil);
}
function _write(
address subject,
bool verified,
uint8 subjectTier,
uint8 subjectSubTier,
uint16 subjectJurisdiction,
uint64 verifiedUntil
) internal {
_identity[subject] = Identity({
verified: verified,
tier: subjectTier,
subTier: subjectSubTier,
jurisdiction: subjectJurisdiction,
verifiedUntil: verifiedUntil,
updatedAt: uint64(block.timestamp)
});
emit IdentityUpserted(subject, verified, subjectTier, subjectSubTier, subjectJurisdiction, verifiedUntil);
}
function setRelayer(address newRelayer) external onlyOwner {
if (newRelayer == address(0)) revert ZeroRelayer();
relayer = newRelayer;
emit RelayerChanged(newRelayer);
}
// ---------------------------------------------------------------------
// Reads (IIdentityRegistry)
// ---------------------------------------------------------------------
function isVerified(address subject) public view returns (bool) {
Identity storage id = _identity[subject];
return id.verified && block.timestamp < id.verifiedUntil;
}
function tier(address subject) external view returns (uint8) {
return _identity[subject].tier;
}
/// Clamped exactly as `CleanverseIdentityRegistry._toUint8` clamps it, so the two
/// implementations of `IIdentityRegistry` answer the same question the same way.
///
/// They did not agree before this: production folds anything above 99 to 0 and
/// blocks pricing, while this mirror returned it verbatim — so an out-of-spec
/// subTier of 150 was refused in production and priced at the top band here. Not
/// exploitable, since only the trusted relayer writes, but two implementations of
/// one interface disagreeing about a value is how a devnet test comes to prove
/// something production does not do.
function subTier(address subject) external view returns (uint8) {
uint8 value = _identity[subject].subTier;
return value > MAX_SUBTIER ? 0 : value;
}
function jurisdiction(address subject) external view returns (uint16) {
return _identity[subject].jurisdiction;
}
function identity(address subject) external view returns (Identity memory) {
return _identity[subject];
}
}