Skip to content
This repository was archived by the owner on Sep 19, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions contracts/foundry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"lib/openzeppelin-contracts": {
"rev": "bd325d56b4c62c9c5c1aff048c37c6bb18ac0290"
},
"lib/openzeppelin-contracts-upgradeable": {
"rev": "a40cb0bda838c2ef3dfc252c179f5c37c32e80c4"
},
"lib\\openzeppelin-contracts-upgradeable": {
"tag": {
"name": "v4.9.5",
Expand Down
12 changes: 10 additions & 2 deletions contracts/src/ActivePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ contract ActivePool is Initializable, IActivePool {
// Last time at which the aggregate batch fees and weighted sum were updated
uint256 public lastAggBatchManagementFeesUpdateTime;

// vault for collateral funds
address public vault;

// --- Events ---

event CollTokenAddressChanged(address _newCollTokenAddress);
Expand All @@ -73,6 +76,7 @@ contract ActivePool is Initializable, IActivePool {
event StabilityPoolAddressChanged(address _newStabilityPoolAddress);
event ActivePoolBoldDebtUpdated(uint256 _recordedDebtSum);
event ActivePoolCollBalanceUpdated(uint256 _collBalance);
event CollateralVaultChanged(address _newCollateralVault);
Copy link

Choose a reason for hiding this comment

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

There is no setter method for vault to have this event emitted.


function initialize(IAddressesRegistry _addressesRegistry) external initializer {
collToken = _addressesRegistry.collToken();
Expand All @@ -83,12 +87,14 @@ contract ActivePool is Initializable, IActivePool {
interestRouter = _addressesRegistry.interestRouter();
boldToken = _addressesRegistry.boldToken();
parameters = _addressesRegistry.parameters();
vault = address(_addressesRegistry.collateralVault());
Copy link

Choose a reason for hiding this comment

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

I believe, let's not add collateralVault's address to addressesRegistry as collateralVault feels like a dynamic address or more likely to be changed frequently compared to core contracts. The collateralVault should be get-able directly from ActivePool's interface via a getter method


emit CollTokenAddressChanged(address(collToken));
emit BorrowerOperationsAddressChanged(borrowerOperationsAddress);
emit TroveManagerAddressChanged(troveManagerAddress);
emit StabilityPoolAddressChanged(address(stabilityPool));
emit DefaultPoolAddressChanged(defaultPoolAddress);
emit CollateralVaultChanged(address(vault));

// Allow funds movements between Liquity contracts
collToken.approve(defaultPoolAddress, type(uint256).max);
Expand Down Expand Up @@ -168,7 +174,9 @@ contract ActivePool is Initializable, IActivePool {

_accountForSendColl(_amount);

collToken.safeTransfer(_account, _amount);
uint256 b = collToken.balanceOf(address(vault));
Copy link

Choose a reason for hiding this comment

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

Didn't understand the purpose of uint256 a and uint256 b

uint256 a = collToken.allowance(address(vault), address(this));
collToken.safeTransferFrom(address(vault), _account, _amount);
}

function sendCollToDefaultPool(uint256 _amount) external override {
Expand All @@ -191,7 +199,7 @@ contract ActivePool is Initializable, IActivePool {
_accountForReceivedColl(_amount);

// Pull Coll tokens from sender
collToken.safeTransferFrom(msg.sender, address(this), _amount);
collToken.safeTransferFrom(msg.sender, address(vault), _amount);
}

function accountForReceivedColl(uint256 _amount) public {
Expand Down
6 changes: 5 additions & 1 deletion contracts/src/AddressesRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ contract AddressesRegistry is Ownable2StepUpgradeable, IAddressesRegistry {
IBoldToken public boldToken;
IWETH public WETH;
IParameters public parameters;

ICollateralVault public collateralVault;

event CollTokenAddressChanged(address _collTokenAddress);
event BorrowerOperationsAddressChanged(address _borrowerOperationsAddress);
event TroveManagerAddressChanged(address _troveManagerAddress);
Expand All @@ -45,6 +46,7 @@ contract AddressesRegistry is Ownable2StepUpgradeable, IAddressesRegistry {
event BoldTokenAddressChanged(address _boldTokenAddress);
event WETHAddressChanged(address _wethAddress);
event ParametersAddressChanged(address _parameters);
event CollateralVaultChanged(address _collateralVault);

function initialize(
address _owner
Expand Down Expand Up @@ -75,6 +77,7 @@ contract AddressesRegistry is Ownable2StepUpgradeable, IAddressesRegistry {
boldToken = _vars.boldToken;
WETH = _vars.WETH;
parameters = _vars.parameters;
collateralVault = _vars.collateralVault;

emit CollTokenAddressChanged(address(_vars.collToken));
emit BorrowerOperationsAddressChanged(address(_vars.borrowerOperations));
Expand All @@ -95,5 +98,6 @@ contract AddressesRegistry is Ownable2StepUpgradeable, IAddressesRegistry {
emit BoldTokenAddressChanged(address(_vars.boldToken));
emit WETHAddressChanged(address(_vars.WETH));
emit ParametersAddressChanged(address(_vars.parameters));
emit CollateralVaultChanged(address(_vars.collateralVault));
}
}
2 changes: 1 addition & 1 deletion contracts/src/BorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1318,7 +1318,7 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio

function _pullCollAndSendToActivePool(IActivePool _activePool, uint256 _amount) internal {
// Send Coll tokens from sender to active pool
collToken.safeTransferFrom(msg.sender, address(_activePool), _amount);
collToken.safeTransferFrom(msg.sender, address(_activePool.vault()), _amount);
Copy link

Choose a reason for hiding this comment

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

So, _activePool.vault() should be the correct way of fetching the collateralVault. Should be removed from addressesRegistry.

// Make sure Active Pool accountancy is right
_activePool.accountForReceivedColl(_amount);
}
Expand Down
53 changes: 53 additions & 0 deletions contracts/src/CollateralVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.24;

import "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol";
import "openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol";

import "./Interfaces/IAddressesRegistry.sol";
import "./Interfaces/ICollateralVault.sol";

contract CollateralVault is Ownable2StepUpgradeable, ICollateralVault {
Copy link

Choose a reason for hiding this comment

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

I kinda recommend standard EIP4626 vault or async vault instead of custom one to avoid integration issues if we create strategies later on for yield

using SafeERC20Upgradeable for IERC20Upgradeable;

IERC20Upgradeable collToken;
// debt is negative when owner has returned more than withdrawed before
int256 public ownerDebt;

function initialize(
address _owner,
IAddressesRegistry _addressesRegistry
) external initializer {
__Ownable2Step_init();
_transferOwnership(_owner);

collToken = _addressesRegistry.collToken();

// Allow funds movements between Liquity contracts
address activePool = address(_addressesRegistry.activePool());
collToken.approve(activePool, type(uint256).max);
address borrowerOps = address(_addressesRegistry.borrowerOperations());
collToken.approve(borrowerOps, type(uint256).max);
address defaultPool = address(_addressesRegistry.defaultPool());
collToken.approve(defaultPool, type(uint256).max);
}

// only owner can withdraw
function withdraw(uint256 amount) external onlyOwner {
_requireBalanceIsEnough(amount);
ownerDebt += int256(amount);
collToken.approve(owner(), amount);
collToken.safeTransfer(owner(), amount);
}

// anyone can top up
function topUp(uint256 amount) external {
ownerDebt -= int256(amount);
collToken.safeTransferFrom(msg.sender, address(this), amount);
}

function _requireBalanceIsEnough(uint256 amount) internal view {
require(amount <= collToken.balanceOf(address(this)), "CollateralVault: Too big withdraw");
}
}
8 changes: 6 additions & 2 deletions contracts/src/DefaultPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ contract DefaultPool is Initializable, IDefaultPool {
IERC20Upgradeable public collToken;
address public troveManagerAddress;
address public activePoolAddress;
address public activePoolVaultAddress;
uint256 internal collBalance; // deposited Coll tracker
uint256 internal BoldDebt; // debt

event CollTokenAddressChanged(address _newCollTokenAddress);
event ActivePoolAddressChanged(address _newActivePoolAddress);
event ActivePoolVaultAddressChanged(address _newActivePoolAddress);
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event DefaultPoolBoldDebtUpdated(uint256 _boldDebt);
event DefaultPoolCollBalanceUpdated(uint256 _collBalance);
Expand All @@ -37,10 +39,12 @@ contract DefaultPool is Initializable, IDefaultPool {
collToken = _addressesRegistry.collToken();
troveManagerAddress = address(_addressesRegistry.troveManager());
activePoolAddress = address(_addressesRegistry.activePool());
activePoolVaultAddress = address(_addressesRegistry.collateralVault());
Copy link

Choose a reason for hiding this comment

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

Caching activePoolVaultAddress in multiple contracts requires multiple setters. I recommend fetching the address directly from activePool to avoid this redundancy. Also, the naming for the external vault is inconsistent across contracts


emit CollTokenAddressChanged(address(collToken));
emit TroveManagerAddressChanged(troveManagerAddress);
emit ActivePoolAddressChanged(activePoolAddress);
emit ActivePoolVaultAddressChanged(activePoolVaultAddress);

// Allow funds movements between Liquity contracts
collToken.approve(activePoolAddress, type(uint256).max);
Expand Down Expand Up @@ -79,8 +83,8 @@ contract DefaultPool is Initializable, IDefaultPool {
uint256 newCollBalance = collBalance + _amount;
collBalance = newCollBalance;

// Pull Coll tokens from ActivePool
collToken.safeTransferFrom(msg.sender, address(this), _amount);
// Pull Coll tokens from ActivePool vault
collToken.safeTransferFrom(activePoolVaultAddress, address(this), _amount);

emit DefaultPoolCollBalanceUpdated(newCollBalance);
}
Expand Down
1 change: 1 addition & 0 deletions contracts/src/Interfaces/IActivePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface IActivePool {
function defaultPoolAddress() external view returns (address);
function borrowerOperationsAddress() external view returns (address);
function troveManagerAddress() external view returns (address);
function vault() external view returns (address);
function interestRouter() external view returns (IInterestRouter);
// We avoid IStabilityPool here in order to prevent creating a dependency cycle that would break flattening
function stabilityPool() external view returns (IBoldRewardsReceiver);
Expand Down
3 changes: 3 additions & 0 deletions contracts/src/Interfaces/IAddressesRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
import "./IActivePool.sol";
import "./IBoldToken.sol";
import "./IBorrowerOperations.sol";
import "./ICollateralVault.sol";
import "./ICollSurplusPool.sol";
import "./IDefaultPool.sol";
import "./IHintHelpers.sol";
Expand Down Expand Up @@ -40,6 +41,7 @@ interface IAddressesRegistry {
IBoldToken boldToken;
IWETH WETH;
IParameters parameters;
ICollateralVault collateralVault;
}

function collToken() external view returns (IERC20MetadataUpgradeable);
Expand All @@ -61,6 +63,7 @@ interface IAddressesRegistry {
function boldToken() external view returns (IBoldToken);
function WETH() external returns (IWETH);
function parameters() external returns (IParameters);
function collateralVault() external view returns(ICollateralVault);

function setAddresses(AddressVars memory _vars) external;
}
6 changes: 6 additions & 0 deletions contracts/src/Interfaces/ICollateralVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface ICollateralVault {
}
2 changes: 1 addition & 1 deletion contracts/src/Parameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.24;

import "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol";

import "./interfaces/IParameters.sol";
import "./Interfaces/IParameters.sol";
import "./Dependencies/Constants.sol";

// For comments please refer to (https://github.com/liquity/bold/blob/main/contracts/src/Dependencies/Constants.sol)
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/TestContracts/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ contract BaseTest is TestAccounts, Logging, TroveId {
GasPool gasPool;
IInterestRouter mockInterestRouter;
IERC20Upgradeable collToken;
ICollateralVault vault;
HintHelpers hintHelpers;
IWETH WETH; // used for gas compensation
WETHZapper wethZapper;
Expand Down Expand Up @@ -573,6 +574,7 @@ contract BaseTest is TestAccounts, Logging, TroveId {
console.log("StabilityPool addr: ", address(stabilityPool));
console.log("TroveManager addr: ", address(troveManager));
console.log("BoldToken addr: ", address(boldToken));
console.log("CollateralVault addr: ", address(vault));
}

function abs(uint256 x, uint256 y) public pure returns (uint256) {
Expand Down
23 changes: 20 additions & 3 deletions contracts/test/TestContracts/Deployment.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "src/ActivePool.sol";
import "src/BoldToken.sol";
import "src/BorrowerOperations.sol";
import "src/CollSurplusPool.sol";
import "src/CollateralVault.sol";
import "src/DefaultPool.sol";
import "src/GasPool.sol";
import "src/HintHelpers.sol";
Expand Down Expand Up @@ -95,6 +96,7 @@ contract TestDeployer is MetadataDeployment {
IInterestRouter interestRouter;
IERC20MetadataUpgradeable collToken;
LiquityContractsDevPools pools;
ICollateralVault collateralVault;
}

struct LiquityContracts {
Expand All @@ -111,6 +113,7 @@ contract TestDeployer is MetadataDeployment {
GasPool gasPool;
IInterestRouter interestRouter;
IERC20MetadataUpgradeable collToken;
ICollateralVault collateralVault;
}

struct Zappers {
Expand All @@ -134,6 +137,7 @@ contract TestDeployer is MetadataDeployment {
address priceFeed;
address gasPool;
address interestRouter;
address collateralVault;
}

struct TroveManagerParams {
Expand Down Expand Up @@ -463,6 +467,9 @@ contract TestDeployer is MetadataDeployment {
addresses.sortedTroves = getAddress(
address(this), abi.encodePacked(type(SortedTroves).creationCode), bSALT
);
addresses.collateralVault = getAddress(
address(this), abi.encodePacked(type(CollateralVault).creationCode), bSALT
);

// Deploy contracts
IAddressesRegistry.AddressVars memory addressVars = IAddressesRegistry.AddressVars({
Expand All @@ -484,7 +491,8 @@ contract TestDeployer is MetadataDeployment {
collateralRegistry: _collateralRegistry,
boldToken: _boldToken,
WETH: _weth,
parameters: _collateralRegistry.parameters()
parameters: _collateralRegistry.parameters(),
collateralVault: ICollateralVault(addresses.collateralVault)
});
contracts.addressesRegistry.setAddresses(addressVars);

Expand All @@ -506,6 +514,8 @@ contract TestDeployer is MetadataDeployment {
CollSurplusPool(address(contracts.pools.collSurplusPool)).initialize(contracts.addressesRegistry);
contracts.sortedTroves = new SortedTroves{salt: bSALT}();
SortedTroves(address(contracts.sortedTroves)).initialize(contracts.addressesRegistry);
contracts.collateralVault = new CollateralVault{salt: bSALT}();
CollateralVault(address(contracts.collateralVault)).initialize(address(this), contracts.addressesRegistry);

assert(address(contracts.borrowerOperations) == addresses.borrowerOperations);
assert(address(contracts.troveManager) == addresses.troveManager);
Expand Down Expand Up @@ -706,6 +716,10 @@ contract TestDeployer is MetadataDeployment {
addresses.sortedTroves = getAddress(
address(this), abi.encodePacked(type(SortedTroves).creationCode), bSALT
);
addresses.collateralVault = getAddress(
address(this), abi.encodePacked(type(CollateralVault).creationCode), SALT
);


contracts.priceFeed =
_deployPriceFeed(_params.branch, _externalAddresses, _oracleParams, addresses.borrowerOperations);
Expand All @@ -730,8 +744,8 @@ contract TestDeployer is MetadataDeployment {
collateralRegistry: _params.collateralRegistry,
boldToken: _params.boldToken,
WETH: _params.weth,
parameters: _params.collateralRegistry.parameters()

parameters: _params.collateralRegistry.parameters(),
collateralVault: ICollateralVault(addresses.collateralVault)
});
contracts.addressesRegistry.setAddresses(addressVars);

Expand All @@ -753,6 +767,9 @@ contract TestDeployer is MetadataDeployment {
CollSurplusPool(address(contracts.collSurplusPool)).initialize(contracts.addressesRegistry);
contracts.sortedTroves = new SortedTroves{salt: bSALT}();
SortedTroves(address(contracts.sortedTroves)).initialize(contracts.addressesRegistry);
contracts.collateralVault = new CollateralVault{salt: SALT}();
CollateralVault(address(contracts.collateralVault)).initialize(address(this), contracts.addressesRegistry);


assert(address(contracts.borrowerOperations) == addresses.borrowerOperations);
assert(address(contracts.troveManager) == addresses.troveManager);
Expand Down
1 change: 1 addition & 0 deletions contracts/test/TestContracts/DevTestSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ contract DevTestSetup is BaseTest {
stabilityPool = contracts.stabilityPool;
troveManager = contracts.troveManager;
troveNFT = contracts.troveNFT;
vault = contracts.collateralVault;
metadataNFT = addressesRegistry.metadataNFT();
mockInterestRouter = contracts.interestRouter;
wethZapper = zappers.wethZapper;
Expand Down
8 changes: 4 additions & 4 deletions contracts/test/redemptions.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -190,15 +190,15 @@ contract Redemptions is DevTestSetup {
uint256 expectedCollDelta = correspondingColl - predictedCollFee;
assertGt(expectedCollDelta, 0);

uint256 activePoolBalBefore = collToken.balanceOf(address(activePool));
uint256 activePoolBalBefore = collToken.balanceOf(address(vault));
uint256 activePoolCollTrackerBefore = activePool.getCollBalance();
assertGt(activePoolBalBefore, 0);
assertGt(activePoolCollTrackerBefore, 0);

redeem(E, redeemAmount);

// Check Active Pool Coll reduced correctly
assertEq(collToken.balanceOf(address(activePool)), activePoolBalBefore - expectedCollDelta);
assertEq(collToken.balanceOf(address(vault)), activePoolBalBefore - expectedCollDelta);
assertEq(activePool.getCollBalance(), activePoolCollTrackerBefore - expectedCollDelta);
}

Expand Down Expand Up @@ -267,15 +267,15 @@ contract Redemptions is DevTestSetup {
uint256 expectedCollDelta = totalCorrespondingColl - totalCollFee;
assertGt(expectedCollDelta, 0);

uint256 activePoolBalBefore = collToken.balanceOf(address(activePool));
uint256 activePoolBalBefore = collToken.balanceOf(address(vault));
uint256 activePoolCollTrackerBefore = activePool.getCollBalance();
assertGt(activePoolBalBefore, 0);
assertGt(activePoolCollTrackerBefore, 0);

redeem(E, totalBoldRedeemAmount);

// Check Active Pool Coll reduced correctly
assertApproxEqAbs(collToken.balanceOf(address(activePool)), activePoolBalBefore - expectedCollDelta, 30);
assertApproxEqAbs(collToken.balanceOf(address(vault)), activePoolBalBefore - expectedCollDelta, 30);
assertApproxEqAbs(activePool.getCollBalance(), activePoolCollTrackerBefore - expectedCollDelta, 30);
}

Expand Down
Loading