Skip to content

Commit

Permalink
fix: handle surplus collateral in Liquity positions
Browse files Browse the repository at this point in the history
* fix: handle surplus collateral in liquity positions

* test: add temp fork test case
  • Loading branch information
SeanJCasey authored Mar 7, 2024
1 parent 3025c55 commit f143dfc
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 5 deletions.
2 changes: 2 additions & 0 deletions contracts/external-interfaces/ILiquityBorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pragma solidity >=0.6.0 <0.9.0;
interface ILiquityBorrowerOperations {
function addColl(address, address) external payable;

function claimCollateral() external;

function closeTrove() external;

function openTrove(uint256, uint256, address, address) external payable;
Expand Down
18 changes: 18 additions & 0 deletions contracts/external-interfaces/ILiquityColSurplusPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0

/*
This file is part of the Enzyme Protocol.
(c) Enzyme Council <[email protected]>
For the full license information, please view the LICENSE
file that was distributed with this source code.
*/

pragma solidity >=0.6.0 <0.9.0;

/// @title ILiquityColSurplusPool Interface
/// @author Enzyme Council <[email protected]>
interface ILiquityColSurplusPool {
function getCollateral(address _account) external view returns (uint256 collateral_);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface ILiquityDebtPosition is IExternalPosition {
RemoveCollateral,
Borrow,
RepayBorrow,
CloseTrove
CloseTrove,
ClaimCollateral
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pragma solidity 0.6.12;

import {IERC20} from "../../../../../external-interfaces/IERC20.sol";
import {ILiquityBorrowerOperations} from "../../../../../external-interfaces/ILiquityBorrowerOperations.sol";
import {ILiquityColSurplusPool} from "../../../../../external-interfaces/ILiquityColSurplusPool.sol";
import {ILiquityTroveManager} from "../../../../../external-interfaces/ILiquityTroveManager.sol";
import {IWETH} from "../../../../../external-interfaces/IWETH.sol";
import {WrappedSafeERC20 as SafeERC20} from "../../../../../utils/0.6.12/open-zeppelin/WrappedSafeERC20.sol";
Expand All @@ -24,15 +25,21 @@ contract LiquityDebtPositionLib is ILiquityDebtPosition, LiquityDebtPositionData
using SafeERC20 for IERC20;

address private immutable LIQUITY_BORROWER_OPERATIONS;
address private immutable LIQUITY_COL_SURPLUS_POOL;
address private immutable LIQUITY_TROVE_MANAGER;

address private immutable LUSD_TOKEN;
address private immutable WETH_TOKEN;

constructor(address _liquityBorrowerOperations, address _liquityTroveManager, address _lusd, address _weth)
public
{
constructor(
address _liquityBorrowerOperations,
address _liquityColSurplusPool,
address _liquityTroveManager,
address _lusd,
address _weth
) public {
LIQUITY_BORROWER_OPERATIONS = _liquityBorrowerOperations;
LIQUITY_COL_SURPLUS_POOL = _liquityColSurplusPool;
LIQUITY_TROVE_MANAGER = _liquityTroveManager;
LUSD_TOKEN = _lusd;
WETH_TOKEN = _weth;
Expand Down Expand Up @@ -73,6 +80,8 @@ contract LiquityDebtPositionLib is ILiquityDebtPosition, LiquityDebtPositionData
__repayBorrow(lusdAmount, upperHint, lowerHint);
} else if (actionId == uint256(Actions.CloseTrove)) {
__closeTrove();
} else if (actionId == uint256(Actions.ClaimCollateral)) {
__claimCollateral();
} else {
revert("receiveCallFromVault: Invalid actionId");
}
Expand All @@ -94,6 +103,16 @@ contract LiquityDebtPositionLib is ILiquityDebtPosition, LiquityDebtPositionData
IERC20(LUSD_TOKEN).safeTransfer(msg.sender, _amount);
}

/// @dev Claims collateral from the collateral surplus pool
function __claimCollateral() private {
ILiquityBorrowerOperations(LIQUITY_BORROWER_OPERATIONS).claimCollateral();

uint256 ethBalance = address(this).balance;

IWETH(WETH_TOKEN).deposit{value: ethBalance}();
IERC20(WETH_TOKEN).safeTransfer(msg.sender, ethBalance);
}

/// @dev Closes a trove
/// It doesn't require to approve LUSD since the balance is directly managed by the borrower operations contract.
function __closeTrove() private {
Expand Down Expand Up @@ -169,7 +188,8 @@ contract LiquityDebtPositionLib is ILiquityDebtPosition, LiquityDebtPositionData
/// @return amounts_ Managed asset amounts
function getManagedAssets() external override returns (address[] memory assets_, uint256[] memory amounts_) {
amounts_ = new uint256[](1);
amounts_[0] = ILiquityTroveManager(LIQUITY_TROVE_MANAGER).getTroveColl(address(this));
amounts_[0] = ILiquityTroveManager(LIQUITY_TROVE_MANAGER).getTroveColl(address(this))
+ ILiquityColSurplusPool(LIQUITY_COL_SURPLUS_POOL).getCollateral(address(this));

// If there's no collateral balance, return empty arrays
if (amounts_[0] == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ contract LiquityDebtPositionParser is IExternalPositionParser, LiquityDebtPositi
assetsToTransfer_[0] = LUSD_TOKEN;
amountsToTransfer_[0] = lusdAmount;
assetsToReceive_[0] = WETH_TOKEN;
} else if (_actionId == uint256(ILiquityDebtPosition.Actions.ClaimCollateral)) {
assetsToReceive_ = new address[](1);
assetsToReceive_[0] = WETH_TOKEN;
}

return (assetsToTransfer_, amountsToTransfer_, assetsToReceive_);
Expand Down
108 changes: 108 additions & 0 deletions tests/tests/protocols/liquity/LiquityDebtPosition.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

import {ILiquityDebtPosition as ILiquityDebtPositionProd} from
"contracts/release/extensions/external-position-manager/external-positions/liquity-debt/ILiquityDebtPosition.sol";

import {IntegrationTest} from "tests/bases/IntegrationTest.sol";

import {ILiquityDebtPositionLib} from "tests/interfaces/internal/ILiquityDebtPositionLib.sol";

address constant LIQUITY_BORROWER_OPERATIONS = 0x24179CD81c9e782A4096035f7eC97fB8B783e007;
address constant LIQUITY_COL_SURPLUS_POOL = 0x3D32e8b97Ed5881324241Cf03b2DA5E2EBcE5521;
address constant LIQUITY_TROVE_MANAGER = 0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2;

contract TestBase is IntegrationTest {
ILiquityDebtPositionLib internal liquityPosition;
address internal comptrollerProxyAddress;
address internal vaultProxyAddress;
address internal fundOwner;

EnzymeVersion internal version;

function setUp() public virtual override {
// TODO: remove fork block number later; needed now for bugfix test
setUpMainnetEnvironment(19378000);

// TODO: update these later to locally-deployed stuff; needed now for bugfix test
liquityPosition = ILiquityDebtPositionLib(0xB5829dfc366EEcDdfec5600a751E1d0906DfBd19);
comptrollerProxyAddress = 0x1A6E4f75EeD0e610C3C0c2F5AF7dA6eE2a3593c6;
vaultProxyAddress = 0x86758FdE8e8924BE2b9Fa440fF9D8C33a4E064A5;
fundOwner = 0x6C48814701c98F0D24b1B891fAC254A817Aadfdf;

// TODO: only testing against v4 for now
version = EnzymeVersion.V4;
}

// DEPLOYMENT HELPERS

function __deployLib() internal returns (address libAddress_) {
bytes memory args = abi.encode(
LIQUITY_BORROWER_OPERATIONS, LIQUITY_COL_SURPLUS_POOL, LIQUITY_TROVE_MANAGER, ETHEREUM_LUSD, wethToken
);

return deployCode("LiquityDebtPositionLib.sol", args);
}

function __deployParser() internal returns (address parserAddress_) {
bytes memory args = abi.encode(LIQUITY_TROVE_MANAGER, ETHEREUM_LUSD, wethToken);

return deployCode("LiquityDebtPositionParser.sol", args);
}

// ACTION HELPERS

function __claimCollateral() internal {
vm.prank(fundOwner);
callOnExternalPositionForVersion({
_version: version,
_comptrollerProxyAddress: comptrollerProxyAddress,
_externalPositionAddress: address(liquityPosition),
_actionId: uint256(ILiquityDebtPositionProd.Actions.ClaimCollateral),
_actionArgs: ""
});
}

function test_temp_surplus_collateral_fix() public {
uint256 surplusCollateralAmount = 247791387261780588060;
uint256 preClaimVaultWethBalance = wethToken.balanceOf(vaultProxyAddress);

// Position should have no value to start
{
(address[] memory assets, uint256[] memory amounts) = liquityPosition.getManagedAssets();
assertEq(assets, new address[](0));
assertEq(amounts, new uint256[](0));
}

address newLib = __deployLib();
address newParser = __deployParser();

// Update the EP contracts
vm.prank(v4ReleaseContracts.externalPositionManager.getOwner());
v4ReleaseContracts.externalPositionManager.updateExternalPositionTypesInfo({
_typeIds: toArray(5),
_libs: toArray(newLib),
_parsers: toArray(newParser)
});

// Position should now have value
{
(address[] memory assets, uint256[] memory amounts) = liquityPosition.getManagedAssets();
assertEq(assets, toArray(address(wethToken)));
assertEq(amounts, toArray(surplusCollateralAmount));
}

// Claim the surplus collateral
__claimCollateral();

// Vault should have received the weth
assertEq(wethToken.balanceOf(vaultProxyAddress), preClaimVaultWethBalance + surplusCollateralAmount);

// Position should now have no value
{
(address[] memory assets, uint256[] memory amounts) = liquityPosition.getManagedAssets();
assertEq(assets, new address[](0));
assertEq(amounts, new uint256[](0));
}
}
}
1 change: 1 addition & 0 deletions tests/utils/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ abstract contract Constants {
address internal constant ETHEREUM_DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address internal constant ETHEREUM_LDO = 0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32;
address internal constant ETHEREUM_LINK = 0x514910771AF9Ca656af840dff83E8264EcF986CA;
address internal constant ETHEREUM_LUSD = 0x5f98805A4E8be255a32880FDeC7F6728C6568bA0;
address internal constant ETHEREUM_MLN = 0xec67005c4E498Ec7f55E092bd1d35cbC47C91892;
address internal constant ETHEREUM_STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
address internal constant ETHEREUM_STKAAVE = 0x4da27a545c0c5B758a6BA100e3a049001de870f5;
Expand Down

0 comments on commit f143dfc

Please sign in to comment.