Skip to content

Commit 383ae7f

Browse files
authored
Add ERC: DeFi Protocol Solvency Proof Mechanism
Merged by EIP-Bot.
1 parent 61a5a09 commit 383ae7f

15 files changed

+2534
-0
lines changed

ERCS/erc-7893.md

Lines changed: 572 additions & 0 deletions
Large diffs are not rendered by default.

assets/erc-7893/LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# MIT License
2+
3+
Copyright (c) 2025 Sean Luis
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

assets/erc-7893/SolvencyProof.sol

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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

Comments
 (0)