Skip to content

feat: Liquity V1 Stability Pool integration for LUSD yield#1011

Open
CelebrityPunks wants to merge 2 commits intoubiquity:developmentfrom
CelebrityPunks:feat/liquity-stability-pool
Open

feat: Liquity V1 Stability Pool integration for LUSD yield#1011
CelebrityPunks wants to merge 2 commits intoubiquity:developmentfrom
CelebrityPunks:feat/liquity-stability-pool

Conversation

@CelebrityPunks
Copy link

Summary

Implements the Liquity V1 Stability Pool integration as specified in #997. This enables the Ubiquity protocol to deposit LUSD collateral into Liquity's Stability Pool to earn ~6.28% APR from ETH liquidation gains and LQTY rewards.

Architecture

Following the existing diamond proxy pattern, this PR adds:

  • ILiquityStabilityPool.sol — Interface for Liquity V1 SP at 0x66017D22b0f8556afDd19e1e5b5f1cbD89a6C337
  • IStabilityPoolFacet.sol — Facet interface with deposit/withdraw/harvest operations and admin configuration setters
  • LibStabilityPool.sol — Diamond storage library containing all business logic, using keccak256("ubiquity.contracts.stability.pool.storage") slot
  • StabilityPoolFacet.sol — Diamond facet with onlyAdmin access control and nonReentrant guards on all state-changing operations
  • MockLiquityStabilityPool.sol — Mock with configurable ETH/LQTY gains and liquidation loss simulation for testing
  • StabilityPoolFacet.t.sol — Foundry test suite with 20+ test cases covering all paths
  • Deploy003_StabilityPool.s.sol — Deployment scripts for both mainnet and development environments

Key Design Decisions

  1. Piggybacked operations: depositToStabilityPool() called after mint, withdrawFromStabilityPool() called before redeem — ETH/LQTY gains auto-harvested on every withdrawal
  2. Harvest via withdrawFromSP(0): Triggers gain collection without reducing the LUSD deposit
  3. Forward gains to treasury: ETH sent via call{value}, LQTY via safeTransfer — treasury can then buyback/compound
  4. Principal tracking: totalPrincipalInPool tracks deposits independent of liquidation-loss-adjusted compounded balance
  5. Double approval pattern: safeApprove(0) then safeApprove(amount) for USDT-compatible approval handling

Storage

struct StabilityPoolStorage {
    address stabilityPool;       // Liquity SP address
    address lusdToken;           // LUSD ERC20
    address lqtyToken;           // LQTY ERC20
    address protocolTreasury;    // Receives harvested gains
    address frontEndTag;         // Liquity frontend kickback tag
    uint256 totalPrincipalInPool; // Total LUSD deposited (pre-loss)
}

Test Plan

  • Configuration: set/validate all address setters with zero-address revert checks
  • Access control: all state-changing functions revert for non-admin callers
  • Deposit: single deposit, multiple deposits, zero amount revert, insufficient balance revert
  • Withdraw: partial, full, excess amount revert, zero amount revert
  • Harvest: ETH gains, LQTY gains, combined gains, gains auto-harvested on withdraw
  • Edge cases: deposit-withdraw-deposit cycle, reentrancy guard verification
  • Mainnet fork test against live Liquity Stability Pool (pending CI integration)

Closes #997

Integrates Liquity V1 Stability Pool for yield generation on LUSD
collateral held by the Ubiquity protocol. Deposits LUSD to earn
ETH liquidation gains and LQTY rewards, with harvested yields
forwarded to the protocol treasury for buybacks/compounding.

Closes ubiquity#997

Files added:
- ILiquityStabilityPool.sol: Liquity SP interface (provideToSP, withdrawFromSP, gain queries)
- IStabilityPoolFacet.sol: Facet interface with deposit/withdraw/harvest + admin setters
- LibStabilityPool.sol: Diamond storage library with all business logic
- StabilityPoolFacet.sol: Diamond facet with access control and reentrancy guards
- MockLiquityStabilityPool.sol: Full mock with configurable gains and loss simulation
- StabilityPoolFacet.t.sol: Foundry test suite (20+ tests covering all paths)
- Deploy003_StabilityPool.s.sol: Deployment scripts for mainnet and development
@CelebrityPunks CelebrityPunks requested a review from rndquu as a code owner March 15, 2026 17:00
@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

📝 Walkthrough

Walkthrough

Adds Liquity V1 Stability Pool integration: new StabilityPoolFacet, LibStabilityPool library, ILiquityStabilityPool and IStabilityPoolFacet interfaces, mocks, tests, and deployment scripts (development and mainnet). The facet exposes deposit, withdraw, harvest, view getters, and admin setters; the library implements diamond-storage state, deposit/withdraw flows, gain forwarding to a protocol treasury, and configuration. Tests cover configuration, deposit/withdraw/harvest flows, edge cases, and reentrancy.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title is clear and specific, directly stating the main feature: Liquity V1 Stability Pool integration for LUSD yield.
Description check ✅ Passed Description is comprehensive with summary, architecture, design decisions, storage, test plan, and issue reference—well-structured and complete.
Linked Issues check ✅ Passed All core objectives from #997 are met: facet interface, library with deposit/withdraw/harvest, admin/reentrancy controls, mock, comprehensive tests, deployment scripts, and principal tracking.
Out of Scope Changes check ✅ Passed All changes directly support Liquity V1 Stability Pool integration; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/contracts/test/diamond/facets/StabilityPoolFacet.t.sol (1)

465-472: This is a placeholder, not a reentrancy test.

assertTrue(true) only proves the test function executed. Use a malicious treasury or stability-pool mock that reenters during gain forwarding and assert the guard reverts.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0046f6e0-6f41-4d1e-983b-f16441bcd073

📥 Commits

Reviewing files that changed from the base of the PR and between be9e35e and ce915b7.

📒 Files selected for processing (8)
  • packages/contracts/migrations/development/Deploy003_StabilityPool.s.sol
  • packages/contracts/migrations/mainnet/Deploy003_StabilityPool.s.sol
  • packages/contracts/src/dollar/facets/StabilityPoolFacet.sol
  • packages/contracts/src/dollar/interfaces/ILiquityStabilityPool.sol
  • packages/contracts/src/dollar/interfaces/IStabilityPoolFacet.sol
  • packages/contracts/src/dollar/libraries/LibStabilityPool.sol
  • packages/contracts/src/dollar/mocks/MockLiquityStabilityPool.sol
  • packages/contracts/test/diamond/facets/StabilityPoolFacet.t.sol

Comment on lines +89 to +118
function depositToPool(uint256 amount) internal {
StabilityPoolStorage storage ss = stabilityPoolStorage();

require(amount > 0, "StabilityPool: zero deposit");
require(
ss.stabilityPool != address(0),
"StabilityPool: pool not set"
);
require(ss.lusdToken != address(0), "StabilityPool: LUSD not set");

IERC20 lusd = IERC20(ss.lusdToken);

// Verify the diamond holds sufficient LUSD
uint256 balance = lusd.balanceOf(address(this));
require(balance >= amount, "StabilityPool: insufficient LUSD");

// Approve the Stability Pool to pull LUSD
lusd.safeApprove(ss.stabilityPool, 0);
lusd.safeApprove(ss.stabilityPool, amount);

// Deposit to Liquity Stability Pool
ILiquityStabilityPool(ss.stabilityPool).provideToSP(
amount,
ss.frontEndTag
);

// Track principal
ss.totalPrincipalInPool += amount;

emit DepositedToStabilityPool(amount);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

cd packages/contracts && find . -name "LibStabilityPool.sol" -type f

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

./src/dollar/libraries/LibStabilityPool.sol

Script executed:

cd packages/contracts && wc -l src/dollar/libraries/LibStabilityPool.sol

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

349 src/dollar/libraries/LibStabilityPool.sol

Script executed:

cd packages/contracts && cat -n src/dollar/libraries/LibStabilityPool.sol

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

     1	// SPDX-License-Identifier: GPL-2.0-or-later
     2	pragma solidity 0.8.19;
     3	
     4	import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
     5	import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
     6	import {ILiquityStabilityPool} from "../interfaces/ILiquityStabilityPool.sol";
     7	
     8	/**
     9	 * `@notice` Library for managing LUSD deposits into the Liquity V1 Stability Pool
    10	 * `@notice` Follows the diamond storage pattern used across the Ubiquity protocol.
    11	 *         Handles deposit, withdrawal, and gain harvesting operations.
    12	 */
    13	library LibStabilityPool {
    14	    using SafeERC20 for IERC20;
    15	
    16	    /// `@notice` Storage slot used to store data for this library
    17	    bytes32 constant STABILITY_POOL_STORAGE_POSITION =
    18	        bytes32(
    19	            uint256(
    20	                keccak256("ubiquity.contracts.stability.pool.storage")
    21	            ) - 1
    22	        ) & ~bytes32(uint256(0xff));
    23	
    24	    /// `@notice` Struct used as a storage for this library
    25	    struct StabilityPoolStorage {
    26	        /// `@notice` Address of the Liquity V1 Stability Pool contract
    27	        address stabilityPool;
    28	        /// `@notice` Address of the LUSD token
    29	        address lusdToken;
    30	        /// `@notice` Address of the LQTY token
    31	        address lqtyToken;
    32	        /// `@notice` Protocol treasury that receives harvested ETH/LQTY gains
    33	        address protocolTreasury;
    34	        /// `@notice` Frontend tag address for Liquity frontend kickback rate
    35	        address frontEndTag;
    36	        /// `@notice` Total LUSD principal deposited (before any liquidation losses)
    37	        uint256 totalPrincipalInPool;
    38	    }
    39	
    40	    /// `@notice` Emitted when LUSD is deposited to the Liquity Stability Pool
    41	    event DepositedToStabilityPool(uint256 amount);
    42	
    43	    /// `@notice` Emitted when LUSD is withdrawn from the Liquity Stability Pool
    44	    event WithdrawnFromStabilityPool(uint256 amount);
    45	
    46	    /// `@notice` Emitted when ETH/LQTY gains are harvested
    47	    event GainsHarvested(
    48	        uint256 ethGain,
    49	        uint256 lqtyGain,
    50	        address treasury
    51	    );
    52	
    53	    /// `@notice` Emitted when the Stability Pool address is configured
    54	    event StabilityPoolAddressSet(address stabilityPool);
    55	
    56	    /// `@notice` Emitted when the LUSD token address is configured
    57	    event LusdTokenAddressSet(address lusdToken);
    58	
    59	    /// `@notice` Emitted when the LQTY token address is configured
    60	    event LqtyTokenAddressSet(address lqtyToken);
    61	
    62	    /// `@notice` Emitted when the protocol treasury address is configured
    63	    event ProtocolTreasurySet(address treasury);
    64	
    65	    /// `@notice` Emitted when the frontend tag is updated
    66	    event FrontEndTagSet(address frontEndTag);
    67	
    68	    /**
    69	     * `@notice` Returns struct used as a storage for this library
    70	     * `@return` ss Struct used as a storage
    71	     */
    72	    function stabilityPoolStorage()
    73	        internal
    74	        pure
    75	        returns (StabilityPoolStorage storage ss)
    76	    {
    77	        bytes32 position = STABILITY_POOL_STORAGE_POSITION;
    78	        assembly {
    79	            ss.slot := position
    80	        }
    81	    }
    82	
    83	    /**
    84	     * `@notice` Deposits LUSD into the Liquity Stability Pool
    85	     * `@dev` LUSD must already be held by this contract (the diamond proxy).
    86	     *      The caller is responsible for transferring LUSD to the diamond before calling.
    87	     * `@param` amount Amount of LUSD to deposit
    88	     */
    89	    function depositToPool(uint256 amount) internal {
    90	        StabilityPoolStorage storage ss = stabilityPoolStorage();
    91	
    92	        require(amount > 0, "StabilityPool: zero deposit");
    93	        require(
    94	            ss.stabilityPool != address(0),
    95	            "StabilityPool: pool not set"
    96	        );
    97	        require(ss.lusdToken != address(0), "StabilityPool: LUSD not set");
    98	
    99	        IERC20 lusd = IERC20(ss.lusdToken);
   100	
   101	        // Verify the diamond holds sufficient LUSD
   102	        uint256 balance = lusd.balanceOf(address(this));
   103	        require(balance >= amount, "StabilityPool: insufficient LUSD");
   104	
   105	        // Approve the Stability Pool to pull LUSD
   106	        lusd.safeApprove(ss.stabilityPool, 0);
   107	        lusd.safeApprove(ss.stabilityPool, amount);
   108	
   109	        // Deposit to Liquity Stability Pool
   110	        ILiquityStabilityPool(ss.stabilityPool).provideToSP(
   111	            amount,
   112	            ss.frontEndTag
   113	        );
   114	
   115	        // Track principal
   116	        ss.totalPrincipalInPool += amount;
   117	
   118	        emit DepositedToStabilityPool(amount);
   119	    }
   120	
   121	    /**
   122	     * `@notice` Withdraws LUSD from the Liquity Stability Pool
   123	     * `@dev` Also triggers collection of any pending ETH/LQTY gains.
   124	     *      The withdrawn LUSD is sent back to the diamond.
   125	     * `@param` amount Amount of LUSD to withdraw
   126	     */
   127	    function withdrawFromPool(uint256 amount) internal {
   128	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   129	
   130	        require(amount > 0, "StabilityPool: zero withdrawal");
   131	        require(
   132	            ss.stabilityPool != address(0),
   133	            "StabilityPool: pool not set"
   134	        );
   135	
   136	        // Get current compounded deposit to validate withdrawal amount
   137	        uint256 compoundedDeposit = ILiquityStabilityPool(ss.stabilityPool)
   138	            .getCompoundedLUSDDeposit(address(this));
   139	        require(
   140	            amount <= compoundedDeposit,
   141	            "StabilityPool: amount exceeds deposit"
   142	        );
   143	
   144	        // Capture pre-withdrawal balances for gain calculation
   145	        uint256 ethBefore = address(this).balance;
   146	        uint256 lqtyBefore = ss.lqtyToken != address(0)
   147	            ? IERC20(ss.lqtyToken).balanceOf(address(this))
   148	            : 0;
   149	
   150	        // Withdraw from Liquity Stability Pool (also collects gains)
   151	        ILiquityStabilityPool(ss.stabilityPool).withdrawFromSP(amount);
   152	
   153	        // Update principal tracking (cap at actual withdrawal to handle liquidation losses)
   154	        if (amount >= ss.totalPrincipalInPool) {
   155	            ss.totalPrincipalInPool = 0;
   156	        } else {
   157	            ss.totalPrincipalInPool -= amount;
   158	        }
   159	
   160	        // Forward harvested gains to treasury
   161	        _forwardGains(ss, ethBefore, lqtyBefore);
   162	
   163	        emit WithdrawnFromStabilityPool(amount);
   164	    }
   165	
   166	    /**
   167	     * `@notice` Harvests ETH and LQTY gains without changing the LUSD deposit
   168	     * `@dev` Calls withdrawFromSP(0) to trigger gain collection without reducing deposit.
   169	     *      Gains are forwarded to the protocol treasury.
   170	     */
   171	    function harvestGains() internal {
   172	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   173	
   174	        require(
   175	            ss.stabilityPool != address(0),
   176	            "StabilityPool: pool not set"
   177	        );
   178	        require(
   179	            ss.protocolTreasury != address(0),
   180	            "StabilityPool: treasury not set"
   181	        );
   182	
   183	        // Capture pre-harvest balances
   184	        uint256 ethBefore = address(this).balance;
   185	        uint256 lqtyBefore = ss.lqtyToken != address(0)
   186	            ? IERC20(ss.lqtyToken).balanceOf(address(this))
   187	            : 0;
   188	
   189	        // Withdraw 0 to trigger gain collection only
   190	        ILiquityStabilityPool(ss.stabilityPool).withdrawFromSP(0);
   191	
   192	        // Forward gains to treasury
   193	        _forwardGains(ss, ethBefore, lqtyBefore);
   194	    }
   195	
   196	    /**
   197	     * `@notice` Returns the current compounded LUSD deposit in the Stability Pool
   198	     * `@return` Compounded LUSD balance (principal minus liquidation losses)
   199	     */
   200	    function getPoolBalance() internal view returns (uint256) {
   201	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   202	
   203	        if (ss.stabilityPool == address(0)) {
   204	            return 0;
   205	        }
   206	
   207	        return
   208	            ILiquityStabilityPool(ss.stabilityPool)
   209	                .getCompoundedLUSDDeposit(address(this));
   210	    }
   211	
   212	    /**
   213	     * `@notice` Returns the total LUSD principal deposited
   214	     * `@return` Total principal in pool
   215	     */
   216	    function getTotalPrincipal() internal view returns (uint256) {
   217	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   218	        return ss.totalPrincipalInPool;
   219	    }
   220	
   221	    /**
   222	     * `@notice` Returns the pending ETH gain from liquidations
   223	     * `@return` ETH gain accrued
   224	     */
   225	    function getETHGain() internal view returns (uint256) {
   226	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   227	
   228	        if (ss.stabilityPool == address(0)) {
   229	            return 0;
   230	        }
   231	
   232	        return
   233	            ILiquityStabilityPool(ss.stabilityPool).getDepositorETHGain(
   234	                address(this)
   235	            );
   236	    }
   237	
   238	    /**
   239	     * `@notice` Returns the pending LQTY reward gain
   240	     * `@return` LQTY gain accrued
   241	     */
   242	    function getLQTYGain() internal view returns (uint256) {
   243	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   244	
   245	        if (ss.stabilityPool == address(0)) {
   246	            return 0;
   247	        }
   248	
   249	        return
   250	            ILiquityStabilityPool(ss.stabilityPool).getDepositorLQTYGain(
   251	                address(this)
   252	            );
   253	    }
   254	
   255	    /**
   256	     * `@notice` Sets the Liquity Stability Pool contract address
   257	     * `@param` _stabilityPool Address of the Liquity Stability Pool
   258	     */
   259	    function setStabilityPoolAddress(address _stabilityPool) internal {
   260	        require(
   261	            _stabilityPool != address(0),
   262	            "StabilityPool: zero address"
   263	        );
   264	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   265	        ss.stabilityPool = _stabilityPool;
   266	        emit StabilityPoolAddressSet(_stabilityPool);
   267	    }
   268	
   269	    /**
   270	     * `@notice` Sets the LUSD token address
   271	     * `@param` _lusdToken Address of the LUSD ERC20 token
   272	     */
   273	    function setLusdTokenAddress(address _lusdToken) internal {
   274	        require(_lusdToken != address(0), "StabilityPool: zero address");
   275	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   276	        ss.lusdToken = _lusdToken;
   277	        emit LusdTokenAddressSet(_lusdToken);
   278	    }
   279	
   280	    /**
   281	     * `@notice` Sets the LQTY token address
   282	     * `@param` _lqtyToken Address of the LQTY ERC20 token
   283	     */
   284	    function setLqtyTokenAddress(address _lqtyToken) internal {
   285	        require(_lqtyToken != address(0), "StabilityPool: zero address");
   286	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   287	        ss.lqtyToken = _lqtyToken;
   288	        emit LqtyTokenAddressSet(_lqtyToken);
   289	    }
   290	
   291	    /**
   292	     * `@notice` Sets the protocol treasury address
   293	     * `@param` _treasury Address of the protocol treasury
   294	     */
   295	    function setProtocolTreasury(address _treasury) internal {
   296	        require(_treasury != address(0), "StabilityPool: zero address");
   297	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   298	        ss.protocolTreasury = _treasury;
   299	        emit ProtocolTreasurySet(_treasury);
   300	    }
   301	
   302	    /**
   303	     * `@notice` Sets the frontend tag for Liquity frontend kickback rewards
   304	     * `@param` _frontEndTag Address of the frontend operator
   305	     */
   306	    function setFrontEndTag(address _frontEndTag) internal {
   307	        StabilityPoolStorage storage ss = stabilityPoolStorage();
   308	        ss.frontEndTag = _frontEndTag;
   309	        emit FrontEndTagSet(_frontEndTag);
   310	    }
   311	
   312	    /**
   313	     * `@notice` Forwards ETH and LQTY gains to the protocol treasury
   314	     * `@param` ss Storage reference
   315	     * `@param` ethBefore ETH balance before the operation
   316	     * `@param` lqtyBefore LQTY balance before the operation
   317	     */
   318	    function _forwardGains(
   319	        StabilityPoolStorage storage ss,
   320	        uint256 ethBefore,
   321	        uint256 lqtyBefore
   322	    ) private {
   323	        uint256 ethGain = address(this).balance - ethBefore;
   324	        uint256 lqtyGain = 0;
   325	
   326	        if (ss.lqtyToken != address(0)) {
   327	            lqtyGain =
   328	                IERC20(ss.lqtyToken).balanceOf(address(this)) -
   329	                lqtyBefore;
   330	        }
   331	
   332	        address treasury = ss.protocolTreasury;
   333	
   334	        // Forward ETH gains
   335	        if (ethGain > 0 && treasury != address(0)) {
   336	            (bool success, ) = treasury.call{value: ethGain}("");
   337	            require(success, "StabilityPool: ETH transfer failed");
   338	        }
   339	
   340	        // Forward LQTY gains
   341	        if (lqtyGain > 0 && treasury != address(0)) {
   342	            IERC20(ss.lqtyToken).safeTransfer(treasury, lqtyGain);
   343	        }
   344	
   345	        if (ethGain > 0 || lqtyGain > 0) {
   346	            emit GainsHarvested(ethGain, lqtyGain, treasury);
   347	        }
   348	    }
   349	}

Script executed:

cd packages/contracts && find . -name "ILiquityStabilityPool.sol" -o -name "*Liquity*" | head -20

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

./src/dollar/interfaces/ILiquityStabilityPool.sol
./src/dollar/mocks/MockLiquityStabilityPool.sol

Script executed:

cd packages/contracts && grep -r "provideToSP\|withdrawFromSP" --include="*.sol" -B 2 -A 2 | head -80

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

src/dollar/facets/StabilityPoolFacet.sol- *      1. User mints Ubiquity Dollars depositing LUSD collateral
src/dollar/facets/StabilityPoolFacet.sol- *      2. Diamond calls depositToStabilityPool() with the received LUSD
src/dollar/facets/StabilityPoolFacet.sol: *      3. LUSD is forwarded to Liquity Stability Pool via provideToSP()
src/dollar/facets/StabilityPoolFacet.sol- *
src/dollar/facets/StabilityPoolFacet.sol- * `@dev` Withdraw flow (on redeem):
src/dollar/facets/StabilityPoolFacet.sol- *      1. User redeems Ubiquity Dollars for LUSD collateral
src/dollar/facets/StabilityPoolFacet.sol- *      2. Diamond calls withdrawFromStabilityPool() for the required LUSD
src/dollar/facets/StabilityPoolFacet.sol: *      3. LUSD is pulled from Liquity Stability Pool via withdrawFromSP()
src/dollar/facets/StabilityPoolFacet.sol- *      4. ETH/LQTY gains are automatically harvested and sent to treasury
src/dollar/facets/StabilityPoolFacet.sol- *
src/dollar/facets/StabilityPoolFacet.sol- * `@dev` Harvest flow (standalone or piggybacked):
src/dollar/facets/StabilityPoolFacet.sol: *      1. Calls withdrawFromSP(0) to trigger gain collection
src/dollar/facets/StabilityPoolFacet.sol- *      2. ETH gains from liquidation absorptions are forwarded to treasury
src/dollar/facets/StabilityPoolFacet.sol- *      3. LQTY rewards are forwarded to treasury for buyback/compounding
--
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@param` _frontEndTag Frontend operator address for kickback rate (use address(0) for no frontend)
src/dollar/interfaces/ILiquityStabilityPool.sol-     */
src/dollar/interfaces/ILiquityStabilityPool.sol:    function provideToSP(uint256 _amount, address _frontEndTag) external;
src/dollar/interfaces/ILiquityStabilityPool.sol-
src/dollar/interfaces/ILiquityStabilityPool.sol-    /**
--
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@param` _amount Amount of LUSD to withdraw. Use type(uint256).max to withdraw entire deposit.
src/dollar/interfaces/ILiquityStabilityPool.sol-     */
src/dollar/interfaces/ILiquityStabilityPool.sol:    function withdrawFromSP(uint256 _amount) external;
src/dollar/interfaces/ILiquityStabilityPool.sol-
src/dollar/interfaces/ILiquityStabilityPool.sol-    /**
--
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        // Deposit to Liquity Stability Pool
src/dollar/libraries/LibStabilityPool.sol:        ILiquityStabilityPool(ss.stabilityPool).provideToSP(
src/dollar/libraries/LibStabilityPool.sol-            amount,
src/dollar/libraries/LibStabilityPool.sol-            ss.frontEndTag
--
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        // Withdraw from Liquity Stability Pool (also collects gains)
src/dollar/libraries/LibStabilityPool.sol:        ILiquityStabilityPool(ss.stabilityPool).withdrawFromSP(amount);
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        // Update principal tracking (cap at actual withdrawal to handle liquidation losses)
--
src/dollar/libraries/LibStabilityPool.sol-    /**
src/dollar/libraries/LibStabilityPool.sol-     * `@notice` Harvests ETH and LQTY gains without changing the LUSD deposit
src/dollar/libraries/LibStabilityPool.sol:     * `@dev` Calls withdrawFromSP(0) to trigger gain collection without reducing deposit.
src/dollar/libraries/LibStabilityPool.sol-     *      Gains are forwarded to the protocol treasury.
src/dollar/libraries/LibStabilityPool.sol-     */
--
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        // Withdraw 0 to trigger gain collection only
src/dollar/libraries/LibStabilityPool.sol:        ILiquityStabilityPool(ss.stabilityPool).withdrawFromSP(0);
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        // Forward gains to treasury
--
src/dollar/mocks/MockLiquityStabilityPool.sol-    }
src/dollar/mocks/MockLiquityStabilityPool.sol-
src/dollar/mocks/MockLiquityStabilityPool.sol:    function provideToSP(uint256 _amount, address) external override {
src/dollar/mocks/MockLiquityStabilityPool.sol-        lusdToken.transferFrom(msg.sender, address(this), _amount);
src/dollar/mocks/MockLiquityStabilityPool.sol-        deposits[msg.sender] += _amount;
src/dollar/mocks/MockLiquityStabilityPool.sol-    }
src/dollar/mocks/MockLiquityStabilityPool.sol-
src/dollar/mocks/MockLiquityStabilityPool.sol:    function withdrawFromSP(uint256 _amount) external override {
src/dollar/mocks/MockLiquityStabilityPool.sol-        uint256 compounded = getCompoundedLUSDDeposit(msg.sender);
src/dollar/mocks/MockLiquityStabilityPool.sol-        uint256 toWithdraw = _amount > compounded ? compounded : _amount;

Script executed:

cd packages/contracts && grep -r "getDepositorETHGain\|getDepositorLQTYGain" --include="*.sol" -B 2 -A 2

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@return` ETH gain accrued by the depositor
src/dollar/interfaces/ILiquityStabilityPool.sol-     */
src/dollar/interfaces/ILiquityStabilityPool.sol:    function getDepositorETHGain(
src/dollar/interfaces/ILiquityStabilityPool.sol-        address _depositor
src/dollar/interfaces/ILiquityStabilityPool.sol-    ) external view returns (uint256);
--
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@return` LQTY gain accrued by the depositor
src/dollar/interfaces/ILiquityStabilityPool.sol-     */
src/dollar/interfaces/ILiquityStabilityPool.sol:    function getDepositorLQTYGain(
src/dollar/interfaces/ILiquityStabilityPool.sol-        address _depositor
src/dollar/interfaces/ILiquityStabilityPool.sol-    ) external view returns (uint256);
--
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        return
src/dollar/libraries/LibStabilityPool.sol:            ILiquityStabilityPool(ss.stabilityPool).getDepositorETHGain(
src/dollar/libraries/LibStabilityPool.sol-                address(this)
src/dollar/libraries/LibStabilityPool.sol-            );
--
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        return
src/dollar/libraries/LibStabilityPool.sol:            ILiquityStabilityPool(ss.stabilityPool).getDepositorLQTYGain(
src/dollar/libraries/LibStabilityPool.sol-                address(this)
src/dollar/libraries/LibStabilityPool.sol-            );
--
src/dollar/mocks/MockLiquityStabilityPool.sol-    }
src/dollar/mocks/MockLiquityStabilityPool.sol-
src/dollar/mocks/MockLiquityStabilityPool.sol:    function getDepositorETHGain(
src/dollar/mocks/MockLiquityStabilityPool.sol-        address _depositor
src/dollar/mocks/MockLiquityStabilityPool.sol-    ) external view override returns (uint256) {
--
src/dollar/mocks/MockLiquityStabilityPool.sol-    }
src/dollar/mocks/MockLiquityStabilityPool.sol-
src/dollar/mocks/MockLiquityStabilityPool.sol:    function getDepositorLQTYGain(
src/dollar/mocks/MockLiquityStabilityPool.sol-        address _depositor
src/dollar/mocks/MockLiquityStabilityPool.sol-    ) external view override returns (uint256) {

Script executed:

cd packages/contracts && cat -n src/dollar/mocks/MockLiquityStabilityPool.sol

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

     1	// SPDX-License-Identifier: MIT
     2	pragma solidity 0.8.19;
     3	
     4	import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
     5	import {ILiquityStabilityPool} from "../interfaces/ILiquityStabilityPool.sol";
     6	
     7	/**
     8	 * `@notice` Mock Liquity Stability Pool for testing
     9	 * `@dev` Simulates deposit/withdrawal behavior, ETH/LQTY gain accrual,
    10	 *      and liquidation-based deposit reduction (compounding).
    11	 */
    12	contract MockLiquityStabilityPool is ILiquityStabilityPool {
    13	    IERC20 public lusdToken;
    14	    IERC20 public lqtyToken;
    15	
    16	    mapping(address => uint256) public deposits;
    17	    mapping(address => uint256) public ethGains;
    18	    mapping(address => uint256) public lqtyGains;
    19	
    20	    /// `@notice` Simulated loss ratio (basis points). 0 = no loss. 500 = 5% loss.
    21	    uint256 public lossRatioBps;
    22	
    23	    constructor(address _lusdToken, address _lqtyToken) {
    24	        lusdToken = IERC20(_lusdToken);
    25	        lqtyToken = IERC20(_lqtyToken);
    26	    }
    27	
    28	    function provideToSP(uint256 _amount, address) external override {
    29	        lusdToken.transferFrom(msg.sender, address(this), _amount);
    30	        deposits[msg.sender] += _amount;
    31	    }
    32	
    33	    function withdrawFromSP(uint256 _amount) external override {
    34	        uint256 compounded = getCompoundedLUSDDeposit(msg.sender);
    35	        uint256 toWithdraw = _amount > compounded ? compounded : _amount;
    36	
    37	        // Reduce deposit
    38	        if (toWithdraw >= deposits[msg.sender]) {
    39	            deposits[msg.sender] = 0;
    40	        } else {
    41	            deposits[msg.sender] -= toWithdraw;
    42	        }
    43	
    44	        // Transfer LUSD back
    45	        if (toWithdraw > 0) {
    46	            lusdToken.transfer(msg.sender, toWithdraw);
    47	        }
    48	
    49	        // Transfer ETH gains
    50	        uint256 ethGain = ethGains[msg.sender];
    51	        if (ethGain > 0) {
    52	            ethGains[msg.sender] = 0;
    53	            (bool success, ) = msg.sender.call{value: ethGain}("");
    54	            require(success, "ETH transfer failed");
    55	        }
    56	
    57	        // Transfer LQTY gains
    58	        uint256 lqtyGain = lqtyGains[msg.sender];
    59	        if (lqtyGain > 0) {
    60	            lqtyGains[msg.sender] = 0;
    61	            lqtyToken.transfer(msg.sender, lqtyGain);
    62	        }
    63	    }
    64	
    65	    function getDepositorETHGain(
    66	        address _depositor
    67	    ) external view override returns (uint256) {
    68	        return ethGains[_depositor];
    69	    }
    70	
    71	    function getDepositorLQTYGain(
    72	        address _depositor
    73	    ) external view override returns (uint256) {
    74	        return lqtyGains[_depositor];
    75	    }
    76	
    77	    function getCompoundedLUSDDeposit(
    78	        address _depositor
    79	    ) public view override returns (uint256) {
    80	        uint256 deposit = deposits[_depositor];
    81	        if (lossRatioBps > 0) {
    82	            uint256 loss = (deposit * lossRatioBps) / 10000;
    83	            return deposit - loss;
    84	        }
    85	        return deposit;
    86	    }
    87	
    88	    // ---- Test helpers ----
    89	
    90	    /// `@notice` Simulate ETH gains from liquidations
    91	    function setETHGain(address _depositor, uint256 _amount) external {
    92	        ethGains[_depositor] = _amount;
    93	    }
    94	
    95	    /// `@notice` Simulate LQTY reward gains
    96	    function setLQTYGain(address _depositor, uint256 _amount) external {
    97	        lqtyGains[_depositor] = _amount;
    98	    }
    99	
   100	    /// `@notice` Simulate liquidation losses (basis points)
   101	    function setLossRatio(uint256 _bps) external {
   102	        lossRatioBps = _bps;
   103	    }
   104	
   105	    /// `@notice` Fund the mock with ETH for gain distribution
   106	    receive() external payable {}
   107	}

Script executed:

cd packages/contracts && cat -n src/dollar/interfaces/ILiquityStabilityPool.sol

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

     1	// SPDX-License-Identifier: GPL-2.0-or-later
     2	pragma solidity 0.8.19;
     3	
     4	/**
     5	 * `@notice` Liquity V1 Stability Pool interface
     6	 * `@dev` Mainnet deployment: 0x66017D22b0f8556afDd19e1e5b5f1cbD89a6C337
     7	 */
     8	interface ILiquityStabilityPool {
     9	    /**
    10	     * `@notice` Deposits LUSD to the Stability Pool
    11	     * `@param` _amount Amount of LUSD to deposit
    12	     * `@param` _frontEndTag Frontend operator address for kickback rate (use address(0) for no frontend)
    13	     */
    14	    function provideToSP(uint256 _amount, address _frontEndTag) external;
    15	
    16	    /**
    17	     * `@notice` Withdraws LUSD from the Stability Pool
    18	     * `@param` _amount Amount of LUSD to withdraw. Use type(uint256).max to withdraw entire deposit.
    19	     */
    20	    function withdrawFromSP(uint256 _amount) external;
    21	
    22	    /**
    23	     * `@notice` Returns the ETH gain for a given depositor
    24	     * `@param` _depositor Address of the depositor
    25	     * `@return` ETH gain accrued by the depositor
    26	     */
    27	    function getDepositorETHGain(
    28	        address _depositor
    29	    ) external view returns (uint256);
    30	
    31	    /**
    32	     * `@notice` Returns the LQTY gain for a given depositor
    33	     * `@param` _depositor Address of the depositor
    34	     * `@return` LQTY gain accrued by the depositor
    35	     */
    36	    function getDepositorLQTYGain(
    37	        address _depositor
    38	    ) external view returns (uint256);
    39	
    40	    /**
    41	     * `@notice` Returns the compounded LUSD deposit for a given depositor.
    42	     *         The compounded deposit reflects the principal minus any losses from
    43	     *         liquidation absorptions.
    44	     * `@param` _depositor Address of the depositor
    45	     * `@return` Compounded LUSD deposit remaining
    46	     */
    47	    function getCompoundedLUSDDeposit(
    48	        address _depositor
    49	    ) external view returns (uint256);
    50	}

Script executed:

cd packages/contracts && grep -r "depositToStabilityPool\|withdrawFromStabilityPool\|harvestGains" --include="*.sol" -A 5 src/dollar/facets/StabilityPoolFacet.sol | head -100

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

 *      2. Diamond calls depositToStabilityPool() with the received LUSD
 *      3. LUSD is forwarded to Liquity Stability Pool via provideToSP()
 *
 * `@dev` Withdraw flow (on redeem):
 *      1. User redeems Ubiquity Dollars for LUSD collateral
 *      2. Diamond calls withdrawFromStabilityPool() for the required LUSD
 *      3. LUSD is pulled from Liquity Stability Pool via withdrawFromSP()
 *      4. ETH/LQTY gains are automatically harvested and sent to treasury
 *
 * `@dev` Harvest flow (standalone or piggybacked):
 *      1. Calls withdrawFromSP(0) to trigger gain collection
--
    function depositToStabilityPool(
        uint256 amount
    ) external nonReentrant onlyAdmin {
        LibStabilityPool.depositToPool(amount);
    }

--
    function withdrawFromStabilityPool(
        uint256 amount
    ) external nonReentrant onlyAdmin {
        LibStabilityPool.withdrawFromPool(amount);
    }

--
    function harvestGains() external nonReentrant onlyAdmin {
        LibStabilityPool.harvestGains();
    }

    //========================
    // Restricted functions
    //========================

Script executed:

cd packages/contracts && grep -r "provideToSP" --include="*.sol" -B 5 -A 10 | grep -A 10 -B 5 "gain\|reward" | head -60

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

src/dollar/facets/StabilityPoolFacet.sol- *
src/dollar/facets/StabilityPoolFacet.sol- * `@dev` Withdraw flow (on redeem):
src/dollar/facets/StabilityPoolFacet.sol- *      1. User redeems Ubiquity Dollars for LUSD collateral
src/dollar/facets/StabilityPoolFacet.sol- *      2. Diamond calls withdrawFromStabilityPool() for the required LUSD
src/dollar/facets/StabilityPoolFacet.sol- *      3. LUSD is pulled from Liquity Stability Pool via withdrawFromSP()
src/dollar/facets/StabilityPoolFacet.sol- *      4. ETH/LQTY gains are automatically harvested and sent to treasury
src/dollar/facets/StabilityPoolFacet.sol- *
src/dollar/facets/StabilityPoolFacet.sol- * `@dev` Harvest flow (standalone or piggybacked):
src/dollar/facets/StabilityPoolFacet.sol- *      1. Calls withdrawFromSP(0) to trigger gain collection
src/dollar/facets/StabilityPoolFacet.sol- *      2. ETH gains from liquidation absorptions are forwarded to treasury
--
src/dollar/interfaces/ILiquityStabilityPool.sol-    /**
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@notice` Deposits LUSD to the Stability Pool
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@param` _amount Amount of LUSD to deposit
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@param` _frontEndTag Frontend operator address for kickback rate (use address(0) for no frontend)
src/dollar/interfaces/ILiquityStabilityPool.sol-     */
src/dollar/interfaces/ILiquityStabilityPool.sol:    function provideToSP(uint256 _amount, address _frontEndTag) external;
src/dollar/interfaces/ILiquityStabilityPool.sol-
src/dollar/interfaces/ILiquityStabilityPool.sol-    /**
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@notice` Withdraws LUSD from the Stability Pool
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@param` _amount Amount of LUSD to withdraw. Use type(uint256).max to withdraw entire deposit.
src/dollar/interfaces/ILiquityStabilityPool.sol-     */
src/dollar/interfaces/ILiquityStabilityPool.sol-    function withdrawFromSP(uint256 _amount) external;
src/dollar/interfaces/ILiquityStabilityPool.sol-
src/dollar/interfaces/ILiquityStabilityPool.sol-    /**
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@notice` Returns the ETH gain for a given depositor
src/dollar/interfaces/ILiquityStabilityPool.sol-     * `@param` _depositor Address of the depositor
--
src/dollar/libraries/LibStabilityPool.sol-        // Approve the Stability Pool to pull LUSD
src/dollar/libraries/LibStabilityPool.sol-        lusd.safeApprove(ss.stabilityPool, 0);
src/dollar/libraries/LibStabilityPool.sol-        lusd.safeApprove(ss.stabilityPool, amount);
src/dollar/libraries/LibStabilityPool.sol-
src/dollar/libraries/LibStabilityPool.sol-        // Deposit to Liquity Stability Pool
src/dollar/libraries/LibStabilityPool.sol:        ILiquityStabilityPool(ss.stabilityPool).provideToSP(
src/dollar/libraries/LibStabilityPool.sol-            amount,
src/dollar/libraries/LibStabilityPool.sol-            ss.frontEndTag

Script executed:

web_search
What does Liquity V1 StabilityPool provideToSP do when depositing to existing position

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 127

stderr:

/bin/bash: line 1: web_search: command not found
/bin/bash: line 2: What: command not found

Liquity V1 StabilityPool provideToSP deposit existing position gains


In Liquity V1, StabilityPool.provideToSP(uint _amount, address _frontEndTag) is the deposit / top-up entrypoint. When you call it while you already have a Stability Pool deposit:

  • Your existing deposit is first “compounded” to account for prior liquidations (compoundedLUSDDeposit = getCompoundedLUSDDeposit(msg.sender)), i.e., your effective LUSD deposit may be lower than what you originally put in. Then the contract sets your new recorded deposit to:
    newDeposit = compoundedLUSDDeposit + _amount (and overwrites the old deposit + refreshes snapshots). [1]

  • Your accumulated gains are paid out on the top-up:

    • It computes your current ETH gain (depositorETHGain = getDepositorETHGain(msg.sender)) and then sends it to you at the end of provideToSP (_sendETHGainToDepositor(depositorETHGain)). [1]
    • It also pays out any LQTY gains before updating your deposit (_payOutLQTYGains(...)). [1]
  • _frontEndTag is only applied on the first deposit (when initialDeposit == 0). If you already have a deposit, calling provideToSP again does not change your front end tag. [1]

So: yes, provideToSP can add to an existing position; and yes, it will claim/payout your pending ETH + LQTY gains as part of that same transaction—it does not auto-compound the ETH gain back into the LUSD deposit (it transfers the ETH to you). [1]

Sources
[1] Liquity V1 StabilityPool verified source (Etherscan) – provideToSP implementation and notes on compounding + payout behavior.


Script executed:

cd packages/contracts && find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "depositToPool\|depositToStabilityPool" 2>/dev/null | head -5

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0


Script executed:

cd packages/contracts && find . -path ./node_modules -prune -o -name "*.test.ts" -type f -print | head -10

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0


Script executed:

cd packages/contracts && grep -r "harvestGains\|depositToPool\|depositToStabilityPool" --include="*.sol" -B 3 -A 3 src/dollar/facets/StabilityPoolFacet.sol

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 0

stdout:

 *
 * `@dev` Deposit flow (on mint):
 *      1. User mints Ubiquity Dollars depositing LUSD collateral
 *      2. Diamond calls depositToStabilityPool() with the received LUSD
 *      3. LUSD is forwarded to Liquity Stability Pool via provideToSP()
 *
 * `@dev` Withdraw flow (on redeem):
--
    //====================

    /// `@inheritdoc` IStabilityPoolFacet
    function depositToStabilityPool(
        uint256 amount
    ) external nonReentrant onlyAdmin {
        LibStabilityPool.depositToPool(amount);
    }

    /// `@inheritdoc` IStabilityPoolFacet
--
    }

    /// `@inheritdoc` IStabilityPoolFacet
    function harvestGains() external nonReentrant onlyAdmin {
        LibStabilityPool.harvestGains();
    }

    //========================

Script executed:

cd packages/contracts && grep -B 10 -A 10 "Only ETH" src/dollar/libraries/LibStabilityPool.sol

Repository: ubiquity/ubiquity-dollar


Repository: ubiquity/ubiquity-dollar
Exit code: 1


Call harvestGains() after topping up existing positions, or snapshot and forward gains within depositToPool().

When Liquity's provideToSP() is called on an existing Stability Pool position, it automatically pays out accumulated ETH and LQTY gains. The current depositToPool() implementation calls provideToSP() but never snapshots balances or forwards these gains to the treasury via _forwardGains(). This causes rewards to strand on the diamond contract. Either trigger the harvest flow after deposit operations that top up existing positions, or refactor depositToPool() to snapshot balances pre-call and forward gains post-call, similar to how withdrawFromPool() and harvestGains() handle gain collection.

- Use pro-rata principal tracking in withdrawFromPool to correctly
  handle liquidation losses (prevents stale totalPrincipalInPool residual)
- Fix mock withdrawFromSP to reduce raw deposits proportionally,
  matching real Liquity pool behavior after partial withdrawals with losses
- Add test for harvestGains revert when treasury is not set
- Replace placeholder reentrancy test with real malicious contract test
- Add test verifying principal tracking after liquidation losses
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/contracts/test/diamond/facets/StabilityPoolFacet.t.sol (1)

466-502: Reentrancy test design is correct but error message expectation deserves a comment.

The test expects "StabilityPool: ETH transfer failed" because the reentrancy guard reverts inside receive(), causing the ETH transfer itself to fail. Consider adding a brief inline comment explaining this causality for future readers.

📝 Optional: Add clarifying comment
         // Harvest should revert: attacker's receive() tries to re-enter,
         // which triggers ReentrancyGuard. The inner revert causes the ETH
         // transfer to fail, surfacing as "StabilityPool: ETH transfer failed".
+        // Note: We don't see "ReentrancyGuard: reentrant call" because
+        // that revert happens inside the attacker's receive(), causing
+        // the low-level call to return false.
         vm.prank(admin);

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 588ddc4f-3ae3-4f01-ad86-0b34394620d7

📥 Commits

Reviewing files that changed from the base of the PR and between ce915b7 and 97c76ac.

📒 Files selected for processing (3)
  • packages/contracts/src/dollar/libraries/LibStabilityPool.sol
  • packages/contracts/src/dollar/mocks/MockLiquityStabilityPool.sol
  • packages/contracts/test/diamond/facets/StabilityPoolFacet.t.sol
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/contracts/src/dollar/mocks/MockLiquityStabilityPool.sol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate Liquity V1 Stability Pool for LUSD Collateral Yield

2 participants