-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathObligationRegistry.sol
More file actions
148 lines (128 loc) · 6.35 KB
/
Copy pathObligationRegistry.sol
File metadata and controls
148 lines (128 loc) · 6.35 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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IIdentityRegistry} from "./interfaces/IIdentityRegistry.sol";
/// @title ObligationRegistry
/// @notice The core mechanic that makes Covenant "confirmed-obligation" financing
/// instead of generic invoice factoring: the OBLIGOR (the party who owes the
/// money) must itself be CVI-verified and must actively confirm the debt
/// on-chain, before a supplier can ever tokenize and finance it. Standard
/// on-chain factoring (Centrifuge/Huma/Polytrade) only verifies the financier;
/// the payer is trusted on paper. Covenant verifies and gates both sides of
/// the same debt.
///
/// Two ways to confirm: a direct call from the obligor's own wallet (the
/// primary demo path), or an EIP-712 signature the obligor produces off-chain
/// and a relayer submits on their behalf (gasless, and de-risks a live 3-wallet
/// demo by letting the obligor's confirmation be pre-signed).
///
/// Honest limitation: this prevents re-confirming or re-tokenizing the SAME
/// confirmed obligation on Covenant (an affirmative act by the debtor, not a
/// hash alone). It does not, and is not pitched to, stop a fabricated invoice
/// or the same real invoice being financed on a different, non-Covenant
/// platform.
contract ObligationRegistry is EIP712 {
struct Confirmation {
address obligor;
address supplier;
uint256 faceValue;
uint64 maturity;
uint8 obligorTier; // Cleanverse-assigned tier, snapshotted AT confirmation time
uint8 obligorSubTier; // integrator-set band, snapshotted AT confirmation time — prices the note
uint16 obligorJurisdiction;
uint64 confirmedAt;
}
// keccak256("ConfirmObligation(address obligor,bytes32 invoiceHash,address supplier,uint256 faceValue,uint64 maturity,uint256 deadline)")
bytes32 public constant CONFIRM_OBLIGATION_TYPEHASH =
keccak256(
"ConfirmObligation(address obligor,bytes32 invoiceHash,address supplier,uint256 faceValue,uint64 maturity,uint256 deadline)"
);
IIdentityRegistry public immutable identityRegistry;
mapping(bytes32 => Confirmation) private _confirmations;
event ObligationConfirmed(
bytes32 indexed key,
address indexed obligor,
address indexed supplier,
bytes32 invoiceHash,
uint256 faceValue,
uint64 maturity,
uint8 obligorTier,
uint8 obligorSubTier
);
error NotVerified(address subject);
error AlreadyConfirmed(bytes32 key);
error SignatureExpired(uint256 deadline);
error InvalidSignature();
constructor(address identityRegistryAddr) EIP712("Covenant-ObligationRegistry", "1") {
identityRegistry = IIdentityRegistry(identityRegistryAddr);
}
/// @notice The confirmation key: one debt, uniquely identified. A second
/// confirmation of the same (invoiceHash, obligor, faceValue, maturity)
/// reverts `AlreadyConfirmed` — this is where "can't double-pledge on
/// Covenant" actually comes from.
function keyFor(bytes32 invoiceHash, address obligor, address supplier, uint256 faceValue, uint64 maturity)
public
pure
returns (bytes32)
{
return keccak256(abi.encode(invoiceHash, obligor, supplier, faceValue, maturity));
}
/// @notice Direct confirmation: called by the obligor's own wallet. This is the
/// primary demo path — a real, visible transaction from the debtor side.
function confirmObligation(bytes32 invoiceHash, address supplier, uint256 faceValue, uint64 maturity)
external
returns (bytes32 key)
{
key = _confirm(msg.sender, invoiceHash, supplier, faceValue, maturity);
}
/// @notice Gasless confirmation: the obligor signs an EIP-712 typed message
/// off-chain; anyone (typically the relayer) can submit it on their behalf
/// before `deadline`. Lets a live 3-wallet demo pre-stage the obligor leg
/// as a genuine cryptographic confirmation without live wallet-switching risk.
function confirmObligationBySig(
address obligor,
bytes32 invoiceHash,
address supplier,
uint256 faceValue,
uint64 maturity,
uint256 deadline,
bytes calldata signature
) external returns (bytes32 key) {
if (block.timestamp > deadline) revert SignatureExpired(deadline);
bytes32 structHash = keccak256(
abi.encode(CONFIRM_OBLIGATION_TYPEHASH, obligor, invoiceHash, supplier, faceValue, maturity, deadline)
);
address signer = ECDSA.recover(_hashTypedDataV4(structHash), signature);
if (signer != obligor) revert InvalidSignature();
key = _confirm(obligor, invoiceHash, supplier, faceValue, maturity);
}
function _confirm(address obligor, bytes32 invoiceHash, address supplier, uint256 faceValue, uint64 maturity)
internal
returns (bytes32 key)
{
if (!identityRegistry.isVerified(obligor)) revert NotVerified(obligor);
key = keyFor(invoiceHash, obligor, supplier, faceValue, maturity);
if (_confirmations[key].confirmedAt != 0) revert AlreadyConfirmed(key);
uint8 obligorTier = identityRegistry.tier(obligor);
uint8 obligorSubTier = identityRegistry.subTier(obligor);
uint16 obligorJurisdiction = identityRegistry.jurisdiction(obligor);
_confirmations[key] = Confirmation({
obligor: obligor,
supplier: supplier,
faceValue: faceValue,
maturity: maturity,
obligorTier: obligorTier,
obligorSubTier: obligorSubTier,
obligorJurisdiction: obligorJurisdiction,
confirmedAt: uint64(block.timestamp)
});
emit ObligationConfirmed(key, obligor, supplier, invoiceHash, faceValue, maturity, obligorTier, obligorSubTier);
}
function isConfirmed(bytes32 key) external view returns (bool) {
return _confirmations[key].confirmedAt != 0;
}
function getConfirmation(bytes32 key) external view returns (Confirmation memory) {
return _confirmations[key];
}
}