@@ -13,43 +13,34 @@ interface IUSDH {
1313/// @title TreasuryRouter — passed as _usdh to LendingPool constructor.
1414///
1515/// Revenue model (MakerDAO-style):
16- /// 100% of repayment/liquidation flows go to the protocol treasury.
17- /// USDH is a stablecoin — burning it destroys borrowing capacity for
18- /// zero token-value benefit. Instead, treasury governance directs funds to:
19- /// Phase 1 (testnet): operations, hackathon prizes, liquidity
20- /// Phase 2 (mainnet): buy DOT/vDOT on open market → distribute to stakers
21- /// Phase 3 (token): buy DOTLEND governance token → burn
16+ /// Only accrued stability fee goes to treasury.
17+ /// Principal is burned — correct USDH peg mechanics.
2218///
23- /// Intercept strategy:
24- /// LendingPool.repay() calls in order:
25- /// (A) usdh.transferFrom(user, address(this), amount) — pull from user
26- /// (B) usdh.burn(amount) — burn from pool
27- ///
28- /// We intercept at (A): when transferFrom(user, lendingPool, amount) is called,
29- /// the router pulls USDH from user directly into itself, sends 100% to
30- /// treasury. When (B) usdh.burn(amount) is called, it's a no-op.
31- ///
32- /// Deploy order:
33- /// 1. Deploy TreasuryRouter(usdhAddress, treasuryAddress)
34- /// 2. Deploy CollateralVault(collateral, oracle)
35- /// 3. Deploy LendingPool(vault, ROUTER_ADDRESS, oracle, collateral)
36- /// 4. vault.setLendingPool(pool)
37- /// 5. router.setLendingPool(pool) ← so router knows pool address
19+ /// Principal tracking:
20+ /// mint(user, amount) is called on borrow — we record principal here.
21+ /// On repay, transferFrom intercepts and splits:
22+ /// principalPortion → burned
23+ /// feePortion (interest) → treasury
3824contract TreasuryRouter is Ownable {
3925 IUSDH public immutable usdh;
4026 address public treasury;
4127 address public lendingPool;
4228
29+ /// @notice Tracks original borrowed principal per user
30+ mapping (address => uint256 ) public principalDebt;
31+
4332 uint256 public totalFeesCollected;
33+ uint256 public totalBurned;
4434
45- event ProtocolFeeCollected (uint256 amount );
35+ event ProtocolFeeCollected (address indexed user , uint256 feeAmount );
36+ event PrincipalBurned (address indexed user , uint256 burnAmount );
4637 event TreasuryUpdated (address indexed newTreasury );
4738 event LendingPoolSet (address indexed pool );
4839
4940 constructor (address _usdh , address _treasury ) {
50- require (_usdh != address (0 ), "TR: zero usdh " );
41+ require (_usdh != address (0 ), "TR: zero usdh " );
5142 require (_treasury != address (0 ), "TR: zero treasury " );
52- usdh = IUSDH (_usdh);
43+ usdh = IUSDH (_usdh);
5344 treasury = _treasury;
5445 }
5546
@@ -65,28 +56,50 @@ contract TreasuryRouter is Ownable {
6556 emit TreasuryUpdated (_treasury);
6657 }
6758
68- /// @notice Called by LendingPool as "transferFrom(user, lendingPool, amount)".
69- /// If `to` == lendingPool, this is a repay/liquidation — intercept
70- /// and route 100% to treasury. Otherwise forward transparently.
59+ /// @notice Called by LendingPool.borrow() — intercept to record principal.
60+ function mint (address to , uint256 amount ) external {
61+ principalDebt[to] += amount;
62+ usdh.mint (to, amount);
63+ }
64+
65+ /// @notice Called by LendingPool.repay() via transferFrom.
66+ /// Splits: principal → burn, interest → treasury.
7167 function transferFrom (address from , address to , uint256 amount ) external returns (bool ) {
7268 if (to == lendingPool && amount > 0 ) {
73- // Intercept: pull from user into router, send 100% to treasury
69+ // Pull USDH from user into router
7470 usdh.transferFrom (from, address (this ), amount);
75- usdh.transfer (treasury, amount);
76- totalFeesCollected += amount;
77- emit ProtocolFeeCollected (amount);
71+
72+ // Split principal vs fee
73+ uint256 principal = principalDebt[from];
74+ uint256 principalPortion = amount <= principal ? amount : principal;
75+ uint256 feePortion = amount - principalPortion;
76+
77+ // Update tracked principal
78+ if (principalPortion > 0 ) {
79+ principalDebt[from] = principal - principalPortion;
80+ }
81+
82+ // Burn the principal
83+ if (principalPortion > 0 ) {
84+ usdh.burn (principalPortion);
85+ totalBurned += principalPortion;
86+ emit PrincipalBurned (from, principalPortion);
87+ }
88+
89+ // Send fee to treasury
90+ if (feePortion > 0 ) {
91+ usdh.transfer (treasury, feePortion);
92+ totalFeesCollected += feePortion;
93+ emit ProtocolFeeCollected (from, feePortion);
94+ }
95+
7896 return true ;
7997 }
8098 return usdh.transferFrom (from, to, amount);
8199 }
82100
83- /// @notice Called by LendingPool after transferFrom — already handled, no-op .
101+ /// @notice Called by LendingPool after transferFrom — already handled.
84102 function burn (uint256 ) external {
85- // Revenue already routed to treasury in transferFrom() — nothing to burn
86- }
87-
88- /// @notice Forwards mint() to real USDH
89- function mint (address to , uint256 amount ) external {
90- usdh.mint (to, amount);
103+ // Principal already burned in transferFrom() — no-op
91104 }
92105}
0 commit comments