muthuk1 commited on
Commit
97b66f6
Β·
verified Β·
1 Parent(s): eb1eb1b

Add contracts/YieldRouterAgent.sol

Browse files
Files changed (1) hide show
  1. contracts/YieldRouterAgent.sol +263 -0
contracts/YieldRouterAgent.sol ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import "@openzeppelin/contracts/access/Ownable.sol";
5
+ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
+ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
8
+
9
+ /**
10
+ * @title YieldRouterAgent
11
+ * @notice On-chain allocation router for the Dynamic RWA Yield Router.
12
+ * Records portfolio state, manages allocations, and enforces
13
+ * risk parameters set by the AgentIdentity8004 contract.
14
+ *
15
+ * @dev Deployed on Mantle L2 (Chain ID 5000).
16
+ * Works in conjunction with:
17
+ * - AgentIdentity8004: Agent reputation and capability attestation
18
+ * - RiskRegistry: On-chain risk parameters and circuit breakers
19
+ *
20
+ * The contract itself does NOT execute swaps β€” it records intent
21
+ * and validates allocations. Actual execution goes through
22
+ * Fluxion/Agni DEX routers via ERC-4337 wallet.
23
+ */
24
+ contract YieldRouterAgent is Ownable, ReentrancyGuard {
25
+ using SafeERC20 for IERC20;
26
+
27
+ // ─────────────────── State ───────────────────────
28
+
29
+ /// @notice Supported RWA assets
30
+ struct Asset {
31
+ address token;
32
+ string symbol;
33
+ uint256 targetWeight; // basis points (10000 = 100%)
34
+ uint256 currentWeight; // basis points
35
+ uint256 lastPrice; // USD price * 1e18
36
+ bool active;
37
+ }
38
+
39
+ /// @notice Allocation record (on-chain audit trail)
40
+ struct AllocationRecord {
41
+ uint256 timestamp;
42
+ uint256[3] weights; // [USDY, mETH, MI4] in bps
43
+ uint256 portfolioValue; // USD * 1e18
44
+ bytes32 reasonHash; // IPFS/content hash of strategy report
45
+ address authorizedBy; // EOA or ERC-4337 wallet that approved
46
+ }
47
+
48
+ /// @notice Circuit breaker states
49
+ enum CircuitBreakerState { CLOSED, OPEN, HALF_OPEN }
50
+
51
+ // Assets
52
+ Asset[3] public assets;
53
+
54
+ // Allocation history
55
+ AllocationRecord[] public allocationHistory;
56
+
57
+ // Agent identity
58
+ address public agentIdentityContract;
59
+ address public riskRegistryContract;
60
+
61
+ // Portfolio
62
+ uint256 public totalPortfolioValue; // USD * 1e18
63
+ uint256 public lastRebalanceTime;
64
+ uint256 public totalRebalances;
65
+
66
+ // Risk parameters
67
+ uint256 public maxSingleAssetWeight = 6000; // 60% in bps
68
+ uint256 public minSingleAssetWeight = 500; // 5% in bps
69
+ uint256 public minRebalanceInterval = 4 hours;
70
+ uint256 public maxSlippageBps = 50; // 0.5%
71
+ CircuitBreakerState public circuitBreaker = CircuitBreakerState.CLOSED;
72
+
73
+ // Authorized operators (ERC-4337 wallet, keeper bots)
74
+ mapping(address => bool) public authorizedOperators;
75
+
76
+ // ─────────────────── Events ───────────────────────
77
+
78
+ event AllocationUpdated(
79
+ uint256 indexed recordId,
80
+ uint256[3] weights,
81
+ uint256 portfolioValue,
82
+ bytes32 reasonHash,
83
+ address indexed authorizedBy
84
+ );
85
+
86
+ event AssetUpdated(uint256 indexed assetIndex, address token, string symbol, bool active);
87
+ event CircuitBreakerTriggered(CircuitBreakerState newState, string reason);
88
+ event OperatorAuthorized(address indexed operator, bool authorized);
89
+ event RiskParametersUpdated(uint256 maxWeight, uint256 minWeight, uint256 minInterval);
90
+
91
+ // ─────────────────── Modifiers ───────────────────
92
+
93
+ modifier onlyAuthorized() {
94
+ require(
95
+ msg.sender == owner() || authorizedOperators[msg.sender],
96
+ "Not authorized"
97
+ );
98
+ _;
99
+ }
100
+
101
+ modifier circuitBreakerClosed() {
102
+ require(
103
+ circuitBreaker != CircuitBreakerState.OPEN,
104
+ "Circuit breaker is OPEN"
105
+ );
106
+ _;
107
+ }
108
+
109
+ // ─────────────────── Constructor ─────────────────
110
+
111
+ constructor(
112
+ address _usdy,
113
+ address _meth,
114
+ address _mi4,
115
+ address _agentIdentity,
116
+ address _riskRegistry
117
+ ) Ownable(msg.sender) {
118
+ assets[0] = Asset(_usdy, "USDY", 4000, 4000, 1e18, true);
119
+ assets[1] = Asset(_meth, "mETH", 3500, 3500, 3200e18, true);
120
+ assets[2] = Asset(_mi4, "MI4", 2500, 2500, 100e18, true);
121
+
122
+ agentIdentityContract = _agentIdentity;
123
+ riskRegistryContract = _riskRegistry;
124
+ lastRebalanceTime = block.timestamp;
125
+ }
126
+
127
+ // ─────────────────── Core Functions ──────────────
128
+
129
+ /**
130
+ * @notice Record a new allocation decision on-chain.
131
+ * @param weights Target weights in basis points [USDY, mETH, MI4]
132
+ * @param portfolioValue Portfolio value in USD * 1e18
133
+ * @param reasonHash Content hash of strategy report (IPFS CID or SHA-256)
134
+ */
135
+ function recordAllocation(
136
+ uint256[3] calldata weights,
137
+ uint256 portfolioValue,
138
+ bytes32 reasonHash
139
+ ) external onlyAuthorized circuitBreakerClosed nonReentrant {
140
+ // Validate weights sum to 10000 bps
141
+ require(
142
+ weights[0] + weights[1] + weights[2] == 10000,
143
+ "Weights must sum to 10000 bps"
144
+ );
145
+
146
+ // Validate position limits
147
+ for (uint256 i = 0; i < 3; i++) {
148
+ require(
149
+ weights[i] >= minSingleAssetWeight,
150
+ "Weight below minimum"
151
+ );
152
+ require(
153
+ weights[i] <= maxSingleAssetWeight,
154
+ "Weight above maximum"
155
+ );
156
+ }
157
+
158
+ // Validate rebalance interval
159
+ require(
160
+ block.timestamp >= lastRebalanceTime + minRebalanceInterval,
161
+ "Rebalance too soon"
162
+ );
163
+
164
+ // Record allocation
165
+ uint256 recordId = allocationHistory.length;
166
+ allocationHistory.push(AllocationRecord({
167
+ timestamp: block.timestamp,
168
+ weights: weights,
169
+ portfolioValue: portfolioValue,
170
+ reasonHash: reasonHash,
171
+ authorizedBy: msg.sender
172
+ }));
173
+
174
+ // Update state
175
+ for (uint256 i = 0; i < 3; i++) {
176
+ assets[i].targetWeight = weights[i];
177
+ assets[i].currentWeight = weights[i];
178
+ }
179
+ totalPortfolioValue = portfolioValue;
180
+ lastRebalanceTime = block.timestamp;
181
+ totalRebalances++;
182
+
183
+ emit AllocationUpdated(recordId, weights, portfolioValue, reasonHash, msg.sender);
184
+ }
185
+
186
+ /**
187
+ * @notice Trigger circuit breaker (emergency stop).
188
+ * @param reason Human-readable reason for the halt
189
+ */
190
+ function triggerCircuitBreaker(string calldata reason) external onlyAuthorized {
191
+ circuitBreaker = CircuitBreakerState.OPEN;
192
+ emit CircuitBreakerTriggered(CircuitBreakerState.OPEN, reason);
193
+ }
194
+
195
+ /**
196
+ * @notice Reset circuit breaker to closed state.
197
+ */
198
+ function resetCircuitBreaker() external onlyOwner {
199
+ circuitBreaker = CircuitBreakerState.CLOSED;
200
+ emit CircuitBreakerTriggered(CircuitBreakerState.CLOSED, "Manual reset");
201
+ }
202
+
203
+ // ─────────────────── Admin Functions ─────────────
204
+
205
+ function setOperator(address operator, bool authorized) external onlyOwner {
206
+ authorizedOperators[operator] = authorized;
207
+ emit OperatorAuthorized(operator, authorized);
208
+ }
209
+
210
+ function setRiskParameters(
211
+ uint256 _maxWeight,
212
+ uint256 _minWeight,
213
+ uint256 _minInterval,
214
+ uint256 _maxSlippage
215
+ ) external onlyOwner {
216
+ require(_maxWeight <= 9000, "Max weight too high");
217
+ require(_minWeight >= 100, "Min weight too low");
218
+ maxSingleAssetWeight = _maxWeight;
219
+ minSingleAssetWeight = _minWeight;
220
+ minRebalanceInterval = _minInterval;
221
+ maxSlippageBps = _maxSlippage;
222
+ emit RiskParametersUpdated(_maxWeight, _minWeight, _minInterval);
223
+ }
224
+
225
+ function updateAsset(
226
+ uint256 index,
227
+ address token,
228
+ string calldata symbol,
229
+ bool active
230
+ ) external onlyOwner {
231
+ require(index < 3, "Invalid asset index");
232
+ assets[index].token = token;
233
+ assets[index].symbol = symbol;
234
+ assets[index].active = active;
235
+ emit AssetUpdated(index, token, symbol, active);
236
+ }
237
+
238
+ // ─────────────────── View Functions ──────────────
239
+
240
+ function getCurrentWeights() external view returns (uint256[3] memory) {
241
+ return [
242
+ assets[0].currentWeight,
243
+ assets[1].currentWeight,
244
+ assets[2].currentWeight
245
+ ];
246
+ }
247
+
248
+ function getAllocationCount() external view returns (uint256) {
249
+ return allocationHistory.length;
250
+ }
251
+
252
+ function getLatestAllocation() external view returns (AllocationRecord memory) {
253
+ require(allocationHistory.length > 0, "No allocations yet");
254
+ return allocationHistory[allocationHistory.length - 1];
255
+ }
256
+
257
+ /**
258
+ * @notice Emergency withdraw tokens (owner only, for contract migration).
259
+ */
260
+ function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
261
+ IERC20(token).safeTransfer(owner(), amount);
262
+ }
263
+ }