contracts

CollateralPool

Overview

CollateralPool is the core vault contract of the BankX protocol. It accepts WETH as collateral, mints and burns XSD, tracks per-user interest, and handles the buyback-and-burn mechanism. All minting and redeeming is subject to the current global_collateral_ratio reported by the PIDController. The contract implements ReentrancyGuard and a block-delay anti-flash-loan mechanism.


Key Concepts

  • collat_XSD — Running total of XSD that has been minted through this pool and is currently outstanding. Used for collateralisation accounting.
  • mintMapping — Per-address MintInfo struct tracking accumulated interest, weighted-average rate, last interaction time, and principal XSD amount.
  • Redemption staging. Redemptions are split into two steps: a redeem call (burns XSD, queues collateral and BankX) and collectRedemption() (transfers queued assets) separated by block_delay blocks.
  • block_delay — Default: 2 blocks. Prevents flash loans that mint XSD, manipulate prices, then redeem in the same transaction.
  • blockDelay modifier — Requires that pid_controller.lastPriceCheck(msg.sender).pricecheck == true AND the last price check block + block_delay <= block.number. After each call with this modifier, pricecheck is reset to false, requiring a new priceCheck() call before the next action.
  • Pause flags. mint_paused, redeem_paused, buyback_paused are owner-controlled.

Architecture

CollateralPool
├── Inherits: Initializable, ReentrancyGuard
├── Calls: XSDStablecoin.pool_mint() / pool_burn_from()
├── Calls: BankXToken.pool_mint() / pool_burn_from()
├── Reads: IPIDController.global_collateral_ratio()
│         IPIDController.neededWETH()
│         IPIDController.neededBankX()
│         IPIDController.bankx_updated_price()
│         IPIDController.xsd_updated_price()
│         IPIDController.bucket3()
│         IPIDController.lastPriceCheck()
├── Calls: IPIDController.setPriceCheck()
├── Calls: CollateralPoolLibrary (interest + buyback math)
├── Holds: WETH (via IWETH deposit/withdraw)
└── Admin: Owner only

State Variables

VariableTypeDescription
WETHaddressWETH contract address
smartcontract_owneraddressContract owner
xsd_contract_addressaddressXSD stablecoin address
bankx_contract_addressaddressBankX token address
xsdweth_pooladdressXSD/WETH AMM pool
bankxweth_pooladdressBankX/WETH AMM pool
pid_addressaddressPID Controller address
collat_XSDuint256Total XSD minted through this pool and outstanding
mint_pausedboolPauses all minting functions
redeem_pausedboolPauses all redemption functions
buyback_pausedboolPauses buyback functions
mintMappingmapping(address => MintInfo)Per-user mint state
redeemBankXTokenBalancesmapping(address => uint256)Queued BankX token amounts (in tokens, not USD)
redeemBankXBalancesmapping(address => uint256)Queued BankX USD values
redeemCollateralBalancesmapping(address => uint256)Queued WETH values in USD
vestingtimestampmapping(address => uint256)(Unused in current version)
unclaimedPoolCollateraluint256Total USD value of WETH queued for redemption
unclaimedPoolBankXuint256Total BankX tokens queued for redemption
collateral_equivalent_d18uint256Scratch variable for buyback calculations
bankx_minted_countuint256Cumulative BankX burned during fractional/algorithmic mints
lastRedeemedmapping(address => uint256)Block number of last redeem call; enforces block_delay in collectRedemption()
block_delayuint256Minimum blocks between redeem and collect. Default: 2.

Core Functions

Views

FunctionMutabilityDescription
collatDollarBalance()viewReturns USD value of WETH held in the pool: WETH.balanceOf(this) * eth_usd_price / 1e6.
availableExcessCollatDV()viewReturns USD value of collateral above what is required at the current CR. Used for buyback eligibility.

Minting (nonReentrant, mintPaused)

FunctionAccessModifiersWhen Valid
mint1t1XSD(uint256 XSD_amount, uint256 deadline)external payableensure, nonReentrant, mintPausedCR == 1000000 (100%)
mintFractionalXSD(uint256 XSD_amount, uint bankx_amount, uint256 deadline)external payableensure, nonReentrant, mintPaused, blockDelay0 < CR < 1000000
mintAlgorithmicXSD(uint256 bankx_amount_d18, uint256 XSD_amount, uint256 deadline)externalensure, nonReentrant, mintPaused, blockDelayCR == 0

mint1t1XSD does not require blockDelay — it is fully collateralised by WETH and presents no flash-loan risk.

Redemption (nonReentrant, redeemPaused, blockDelay)

FunctionAccessWhen Valid
redeem1t1XSD(uint256 XSD_amount, uint256 COLLATERAL_out_min, uint256 deadline)externalCR == 1000000, bucket3 == false
redeemFractionalXSD(uint256 XSD_amount, uint256 BankX_out_min, uint256 COLLATERAL_out_min, uint256 deadline)external0 < CR < 1000000, bucket3 == false
redeemAlgorithmicXSD(uint256 XSD_amount, uint256 BankX_out_min, uint256 deadline)externalCR == 0, bucket3 == false
collectRedemption()externalAfter block_delay blocks, bucket3 == false

All redeem functions enforce XSD_amount <= mintMapping[msg.sender].amount (overredemption guard).

Buyback

FunctionAccessModifiersDescription
buyBackBankX(uint256 BankX_amount, uint256 COLLATERAL_out_min, uint256 deadline)externalblockDelay, ensureBurns BankX in exchange for excess WETH collateral.
buyBackXSD(uint256 XSD_amount, uint256 collateral_out_min, uint256 deadline)externalblockDelay, ensureBurns XSD in exchange for excess WETH collateral.

Admin (onlyByOwner)

FunctionDescription
setPoolParameters(uint256 new_block_delay, bool _mint_paused, bool _redeem_paused, bool _buyback_paused)Adjusts block delay and pause states.
setPIDController(address new_pid_address)Updates PID Controller reference.
setSmartContractOwner(address)Transfers ownership.
renounceOwnership()Irreversibly sets owner to address(0).
resetAddresses(...)Bulk address reset (XSD, BankX, pools, WETH).

Events

EventParametersWhen Emitted
PoolParametersSet(uint256, bool, bool, bool)new_block_delay, mint_paused, redeem_paused, buyback_pausedsetPoolParameters()
RedemptionCollected(address, uint, uint)user, bankxAmount, collateralAmountcollectRedemption()

Security Considerations

  • nonReentrant on all mint/redeem/collect. All state-changing user functions in CollateralPool carry OpenZeppelin's nonReentrant guard.
  • Two-step redemption prevents flash loans. XSD is burned in the redeem call. Collateral and BankX are only transferred block_delay blocks later via collectRedemption(). An attacker cannot mint, manipulate, and redeem in a single transaction.
  • bucket3 redemption block. If pid_controller.bucket3() == true, all redeem functions and collectRedemption() revert. Redemptions resume only when the collateral deficit is resolved.
  • blockDelay requires a prior priceCheck(). Fractional minting, algorithmic minting, and all redeems require the caller to have a valid priceCheck recorded in the PID Controller within the last block_delay blocks.
  • Overredemption guard. XSD_amount <= mintMapping[msg.sender].amount ensures a user cannot redeem more XSD than they personally minted, preventing draining the pool via fabricated redemptions.
  • receive() restricted to WETH. The fallback receive() function only accepts ETH from the WETH contract. Direct ETH sends revert.

Integration Guide

ICollateralPool pool = ICollateralPool(payable(COLLATERAL_POOL_ADDRESS));
 
// Step 1: Call priceCheck() on PID Controller to satisfy blockDelay modifier
IPIDController(PID_ADDRESS).priceCheck();
 
// Step 2a: 1:1 mint (CR = 100%)
pool.mint1t1XSD{value: ethAmount}(XSD_amount, block.timestamp + 300);
 
// Step 2b: Fractional mint (0 < CR < 100%)
IERC20(BANKX_ADDRESS).approve(COLLATERAL_POOL_ADDRESS, bankx_amount);
pool.mintFractionalXSD{value: ethAmount}(XSD_amount, bankx_amount, block.timestamp + 300);
 
// Step 3: Fractional redeem
IERC20(XSD_ADDRESS).approve(COLLATERAL_POOL_ADDRESS, XSD_amount);
pool.redeemFractionalXSD(XSD_amount, bankx_min, collateral_min, block.timestamp + 300);
 
// Step 4: Wait block_delay blocks, then collect
// (Must call priceCheck() again before collectRedemption)
IPIDController(PID_ADDRESS).priceCheck();
pool.collectRedemption();