1+ // SPDX-License-Identifier: MIT
2+ pragma solidity ^ 0.8.20 ;
3+
4+ import "./ISolvencyProof.sol " ;
5+ import "@openzeppelin/contracts/access/Ownable.sol " ;
6+ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol " ;
7+
8+ /**
9+ * @title SolvencyProof
10+ * @author Sean Luis (@SeanLuis) <[email protected] > 11+ * @notice Implementation of DeFi Protocol Solvency Proof Standard (EIP-DRAFT)
12+ * @dev This contract implements ISolvencyProof interface for tracking and verifying protocol solvency
13+ * It includes asset/liability tracking, solvency ratio calculations, and historical metrics
14+ */
15+ contract SolvencyProof is ISolvencyProof , Ownable , ReentrancyGuard {
16+ // === Constants ===
17+ /// @notice Base multiplier for ratio calculations (100% = 10000)
18+ uint256 private constant RATIO_DECIMALS = 10000 ;
19+
20+ /// @notice Minimum solvency ratio required (105%)
21+ uint256 private constant MIN_SOLVENCY_RATIO = 10500 ;
22+
23+ /// @notice Critical threshold for emergency measures (102%)
24+ uint256 private constant CRITICAL_RATIO = 10200 ;
25+
26+ // === State Variables ===
27+ /// @notice Current state of protocol assets
28+ ProtocolAssets private currentAssets;
29+
30+ /// @notice Current state of protocol liabilities
31+ ProtocolLiabilities private currentLiabilities;
32+
33+ /// @notice Mapping of authorized price oracles
34+ /// @dev address => isAuthorized
35+ mapping (address => bool ) public assetOracles;
36+
37+ /**
38+ * @notice Structure for storing historical solvency metrics
39+ * @dev Used to track protocol's financial health over time
40+ * @param timestamp Time when metrics were recorded
41+ * @param solvencyRatio Calculated solvency ratio at that time
42+ * @param assets Snapshot of protocol assets
43+ * @param liabilities Snapshot of protocol liabilities
44+ */
45+ struct HistoricalMetric {
46+ uint256 timestamp;
47+ uint256 solvencyRatio;
48+ ProtocolAssets assets;
49+ ProtocolLiabilities liabilities;
50+ }
51+
52+ /// @notice Array storing historical solvency metrics
53+ HistoricalMetric[] private metricsHistory;
54+
55+ // === Events ===
56+ /// @notice Emitted when an oracle's authorization status changes
57+ /// @param oracle Address of the oracle
58+ /// @param authorized New authorization status
59+ event OracleUpdated (address indexed oracle , bool authorized );
60+
61+ /**
62+ * @notice Contract constructor
63+ * @dev Initializes Ownable with msg.sender as owner
64+ */
65+ constructor () Ownable (msg .sender ) {}
66+
67+ /**
68+ * @notice Restricts function access to authorized oracles
69+ * @dev Throws if called by non-authorized address
70+ */
71+ modifier onlyOracle () {
72+ require (assetOracles[msg .sender ], "Not authorized oracle " );
73+ _;
74+ }
75+
76+ // === External Functions ===
77+ /// @inheritdoc ISolvencyProof
78+ function getProtocolAssets () external view returns (ProtocolAssets memory ) {
79+ return currentAssets;
80+ }
81+
82+ /// @inheritdoc ISolvencyProof
83+ function getProtocolLiabilities () external view returns (ProtocolLiabilities memory ) {
84+ return currentLiabilities;
85+ }
86+
87+ /// @inheritdoc ISolvencyProof
88+ function getSolvencyRatio () external view returns (uint256 ) {
89+ return _calculateSolvencyRatio ();
90+ }
91+
92+ /// @inheritdoc ISolvencyProof
93+ function verifySolvency () external view returns (bool isSolvent , uint256 healthFactor ) {
94+ uint256 ratio = _calculateSolvencyRatio ();
95+ return (ratio >= MIN_SOLVENCY_RATIO, ratio);
96+ }
97+
98+ /// @inheritdoc ISolvencyProof
99+ function getSolvencyHistory (uint256 startTime , uint256 endTime )
100+ external
101+ view
102+ returns (
103+ uint256 [] memory timestamps ,
104+ uint256 [] memory ratios ,
105+ ProtocolAssets[] memory assets ,
106+ ProtocolLiabilities[] memory liabilities
107+ )
108+ {
109+ uint256 count = 0 ;
110+ for (uint256 i = 0 ; i < metricsHistory.length ; i++ ) {
111+ if (metricsHistory[i].timestamp >= startTime &&
112+ metricsHistory[i].timestamp <= endTime) {
113+ count++ ;
114+ }
115+ }
116+
117+ timestamps = new uint256 [](count);
118+ ratios = new uint256 [](count);
119+ assets = new ProtocolAssets [](count);
120+ liabilities = new ProtocolLiabilities [](count);
121+ uint256 index = 0 ;
122+
123+ for (uint256 i = 0 ; i < metricsHistory.length && index < count; i++ ) {
124+ if (metricsHistory[i].timestamp >= startTime &&
125+ metricsHistory[i].timestamp <= endTime) {
126+ timestamps[index] = metricsHistory[i].timestamp;
127+ ratios[index] = metricsHistory[i].solvencyRatio;
128+ assets[index] = metricsHistory[i].assets;
129+ liabilities[index] = metricsHistory[i].liabilities;
130+ index++ ;
131+ }
132+ }
133+
134+ return (timestamps, ratios, assets, liabilities);
135+ }
136+
137+ /// @inheritdoc ISolvencyProof
138+ function updateAssets (
139+ address [] calldata tokens ,
140+ uint256 [] calldata amounts ,
141+ uint256 [] calldata values
142+ ) external onlyOracle nonReentrant {
143+ require (tokens.length == amounts.length && amounts.length == values.length ,
144+ "Array lengths mismatch " );
145+
146+ currentAssets = ProtocolAssets ({
147+ tokens: tokens,
148+ amounts: amounts,
149+ values: values,
150+ timestamp: block .timestamp
151+ });
152+
153+ _updateMetrics ();
154+ }
155+
156+ /// @inheritdoc ISolvencyProof
157+ function updateLiabilities (
158+ address [] calldata tokens ,
159+ uint256 [] calldata amounts ,
160+ uint256 [] calldata values
161+ ) external onlyOracle nonReentrant {
162+ require (tokens.length == amounts.length && amounts.length == values.length ,
163+ "Array lengths mismatch " );
164+
165+ currentLiabilities = ProtocolLiabilities ({
166+ tokens: tokens,
167+ amounts: amounts,
168+ values: values,
169+ timestamp: block .timestamp
170+ });
171+
172+ _updateMetrics ();
173+ }
174+
175+ /**
176+ * @notice Updates oracle authorization status
177+ * @dev Only callable by contract owner
178+ * @param oracle Address of the oracle to update
179+ * @param authorized New authorization status
180+ */
181+ function setOracle (address oracle , bool authorized ) external onlyOwner {
182+ require (oracle != address (0 ), "Invalid oracle address " );
183+ assetOracles[oracle] = authorized;
184+ emit OracleUpdated (oracle, authorized);
185+ }
186+
187+ // === Internal Functions ===
188+ /**
189+ * @notice Calculates current solvency ratio
190+ * @dev Ratio = (Total Assets / Total Liabilities) × RATIO_DECIMALS
191+ * @return Current solvency ratio with RATIO_DECIMALS precision
192+ */
193+ function _calculateSolvencyRatio () internal view returns (uint256 ) {
194+ uint256 totalAssets = _sumArray (currentAssets.values);
195+ uint256 totalLiabilities = _sumArray (currentLiabilities.values);
196+
197+ if (totalLiabilities == 0 ) {
198+ return totalAssets > 0 ? RATIO_DECIMALS * 2 : RATIO_DECIMALS;
199+ }
200+
201+ return (totalAssets * RATIO_DECIMALS) / totalLiabilities;
202+ }
203+
204+ /**
205+ * @notice Updates protocol metrics and emits relevant events
206+ * @dev Called after asset or liability updates
207+ */
208+ function _updateMetrics () internal {
209+ uint256 totalAssets = _sumArray (currentAssets.values);
210+ uint256 totalLiabilities = _sumArray (currentLiabilities.values);
211+ uint256 ratio = _calculateSolvencyRatio ();
212+
213+ // Debug log
214+ emit SolvencyMetricsUpdated (
215+ totalAssets,
216+ totalLiabilities,
217+ ratio,
218+ block .timestamp
219+ );
220+
221+ metricsHistory.push (HistoricalMetric ({
222+ timestamp: block .timestamp ,
223+ solvencyRatio: ratio,
224+ assets: currentAssets,
225+ liabilities: currentLiabilities
226+ }));
227+
228+ // Update alerts based on actual ratio
229+ if (ratio < CRITICAL_RATIO) {
230+ emit RiskAlert ("CRITICAL " , ratio, totalAssets, totalLiabilities);
231+ } else if (ratio < MIN_SOLVENCY_RATIO) {
232+ emit RiskAlert ("HIGH_RISK " , ratio, totalAssets, totalLiabilities);
233+ }
234+ }
235+
236+ /**
237+ * @notice Sums all values in an array
238+ * @param array Array of uint256 values to sum
239+ * @return sum Total sum of array values
240+ */
241+ function _sumArray (uint256 [] memory array ) internal pure returns (uint256 ) {
242+ uint256 sum = 0 ;
243+ for (uint256 i = 0 ; i < array.length ; i++ ) {
244+ sum += array[i];
245+ }
246+ return sum;
247+ }
248+ }
0 commit comments