Skip to content
Merged
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
193 changes: 188 additions & 5 deletions spot-vaults/contracts/BillBroker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ import { UnacceptableSwap, UnreliablePrice, UnexpectedDecimals, InvalidPerc, Inv
* Lenders can buy perps from the bill broker contract when it's under-priced,
* hold the perp tokens until the market price recovers and sell it back to the bill broker contract.
*
* Single Sided deposits:
* The pool also supports single sided deposits with either perps or usd tokens
* insofar as it brings the pool back into balance.
*
*/
contract BillBroker is
Expand Down Expand Up @@ -112,6 +115,29 @@ contract BillBroker is
/// the swap fees transition from a flat percentage fee to a linear function.
Range public arSoftBound;

//--------------------------------------------------------------------------
// Events

/// @notice Emitted when a user deposits USD tokens to mint LP tokens.
/// @param usdAmtIn The amount of USD tokens deposited.
/// @param preOpState The reserve state before the deposit operation.
event DepositUSD(uint256 usdAmtIn, ReserveState preOpState);

/// @notice Emitted when a user deposits Perp tokens to mint LP tokens.
/// @param perpAmtIn The amount of Perp tokens deposited.
/// @param preOpState The reserve state before the deposit operation.
event DepositPerp(uint256 perpAmtIn, ReserveState preOpState);

/// @notice Emitted when a user swaps Perp tokens for USD tokens.
/// @param perpAmtIn The amount of Perp tokens swapped in.
/// @param preOpState The reserve state before the swap operation.
event SwapPerpsForUSD(uint256 perpAmtIn, ReserveState preOpState);

/// @notice Emitted when a user swaps USD tokens for Perp tokens.
/// @param usdAmtIn The amount of USD tokens swapped in.
/// @param preOpState The reserve state before the swap operation.
event SwapUSDForPerps(uint256 usdAmtIn, ReserveState preOpState);

//--------------------------------------------------------------------------
// Modifiers

Expand Down Expand Up @@ -289,6 +315,90 @@ contract BillBroker is
_mint(msg.sender, mintAmt);
}

/// @notice Single sided usd token deposit and mint LP tokens.
/// @param usdAmtIn The amount of usd tokens to be deposited.
/// @param postOpAssetRatioMax The system asset ratio can be no higher than this value after deposit.
/// @return mintAmt The amount of LP tokens minted.
function depositUSD(
uint256 usdAmtIn,
uint256 postOpAssetRatioMax
) external nonReentrant whenNotPaused returns (uint256 mintAmt) {
ReserveState memory preOpState = reserveState();
uint256 preOpAssetRatio = assetRatio(preOpState);
uint256 postOpAssetRatio = assetRatio(
ReserveState({
usdBalance: preOpState.usdBalance + usdAmtIn,
perpBalance: preOpState.perpBalance,
usdPrice: preOpState.usdPrice,
perpPrice: preOpState.perpPrice
})
);

// We allow minting only pool is underweight usd
if (preOpAssetRatio >= ONE || postOpAssetRatio > ONE) {
return 0;
}

mintAmt = computeMintAmtWithUSD(usdAmtIn, preOpState);
if (mintAmt <= 0) {
return 0;
}
if (postOpAssetRatio > postOpAssetRatioMax) {
revert SlippageTooHigh();
}

// Transfer usd tokens from the user
usd.safeTransferFrom(msg.sender, address(this), usdAmtIn);

// mint LP tokens to the user
_mint(msg.sender, mintAmt);

// Emit deposit info
emit DepositUSD(usdAmtIn, preOpState);
}

/// @notice Single sided perp token deposit and mint LP tokens.
/// @param perpAmtIn The amount of perp tokens to be deposited.
/// @param postOpAssetRatioMin The system asset ratio can be no lower than this value after deposit.
/// @return mintAmt The amount of LP tokens minted.
function depositPerp(
uint256 perpAmtIn,
uint256 postOpAssetRatioMin
) external nonReentrant whenNotPaused returns (uint256 mintAmt) {
ReserveState memory preOpState = reserveState();
uint256 preOpAssetRatio = assetRatio(preOpState);
uint256 postOpAssetRatio = assetRatio(
ReserveState({
usdBalance: preOpState.usdBalance,
perpBalance: preOpState.perpBalance + perpAmtIn,
usdPrice: preOpState.usdPrice,
perpPrice: preOpState.perpPrice
})
);

// We allow minting only pool is underweight perp
if (preOpAssetRatio <= ONE || postOpAssetRatio < ONE) {
return 0;
}

mintAmt = computeMintAmtWithPerp(perpAmtIn, preOpState);
if (mintAmt <= 0) {
return 0;
}
if (postOpAssetRatio < postOpAssetRatioMin) {
revert SlippageTooHigh();
}

// Transfer perp tokens from the user
perp.safeTransferFrom(msg.sender, address(this), perpAmtIn);

// mint LP tokens to the user
_mint(msg.sender, mintAmt);

// Emit deposit info
emit DepositPerp(perpAmtIn, preOpState);
}

/// @notice Burns LP tokens and redeems usd and perp tokens.
/// @param burnAmt The LP tokens to be burnt.
/// @return usdAmtOut The amount usd tokens returned.
Expand Down Expand Up @@ -323,10 +433,11 @@ contract BillBroker is
uint256 perpAmtMin
) external nonReentrant whenNotPaused returns (uint256 perpAmtOut) {
// compute perp amount out
ReserveState memory preOpState = reserveState();
uint256 protocolFeePerpAmt;
(perpAmtOut, , protocolFeePerpAmt) = computeUSDToPerpSwapAmt(
usdAmtIn,
reserveState()
preOpState
);
if (usdAmtIn <= 0 || perpAmtOut <= 0) {
revert UnacceptableSwap();
Expand All @@ -345,6 +456,9 @@ contract BillBroker is

// transfer perps out to the user
perp.safeTransfer(msg.sender, perpAmtOut);

// Emit swap info
emit SwapUSDForPerps(usdAmtIn, preOpState);
}

/// @notice Swaps perp tokens from the user for usd tokens from the reserve.
Expand All @@ -356,11 +470,9 @@ contract BillBroker is
uint256 usdAmtMin
) external nonReentrant whenNotPaused returns (uint256 usdAmtOut) {
// Compute swap amount
ReserveState memory preOpState = reserveState();
uint256 protocolFeeUsdAmt;
(usdAmtOut, , protocolFeeUsdAmt) = computePerpToUSDSwapAmt(
perpAmtIn,
reserveState()
);
(usdAmtOut, , protocolFeeUsdAmt) = computePerpToUSDSwapAmt(perpAmtIn, preOpState);
if (perpAmtIn <= 0 || usdAmtOut <= 0) {
revert UnacceptableSwap();
}
Expand All @@ -378,11 +490,30 @@ contract BillBroker is

// transfer usd out to the user
usd.safeTransfer(msg.sender, usdAmtOut);

// Emit swap info
emit SwapPerpsForUSD(perpAmtIn, preOpState);
}

//-----------------------------------------------------------------------------
// Public methods

/// @notice Computes the amount of LP tokens minted,
/// when the given number of usd tokens are deposited.
/// @param usdAmtIn The amount of usd tokens deposited.
/// @return mintAmt The amount of LP tokens minted.
function computeMintAmtWithUSD(uint256 usdAmtIn) public returns (uint256 mintAmt) {
return computeMintAmtWithUSD(usdAmtIn, reserveState());
}

/// @notice Computes the amount of LP tokens minted,
/// when the given number of perp tokens are deposited.
/// @param perpAmtIn The amount of perp tokens deposited.
/// @return mintAmt The amount of LP tokens minted.
function computeMintAmtWithPerp(uint256 perpAmtIn) public returns (uint256 mintAmt) {
return computeMintAmtWithPerp(perpAmtIn, reserveState());
}

/// @notice Computes the amount of usd tokens swapped out,
/// when the given number of perp tokens are sent in.
/// @param perpAmtIn The amount of perp tokens swapped in.
Expand Down Expand Up @@ -508,6 +639,58 @@ contract BillBroker is
mintAmt = mintAmt.mulDiv(ONE - fees.mintFeePerc, ONE);
}

/// @notice Computes the amount of LP tokens minted,
/// when the given number of usd tokens are deposited.
/// @param usdAmtIn The amount of usd tokens deposited.
/// @param s The current reserve state.
/// @return mintAmt The amount of LP tokens minted.
function computeMintAmtWithUSD(
uint256 usdAmtIn,
ReserveState memory s
) public view returns (uint256) {
if (usdAmtIn <= 0) {
return 0;
}

uint256 valueIn = s.usdPrice.mulDiv(usdAmtIn, usdUnitAmt);
uint256 totalReserveVal = (s.usdPrice.mulDiv(s.usdBalance, usdUnitAmt) +
s.perpPrice.mulDiv(s.perpBalance, perpUnitAmt));

return
(totalReserveVal > 0)
? valueIn.mulDiv(totalSupply(), totalReserveVal).mulDiv(
ONE - fees.mintFeePerc,
ONE
)
: 0;
}

/// @notice Computes the amount of LP tokens minted,
/// when the given number of perp tokens are deposited.
/// @param perpAmtIn The amount of perp tokens deposited.
/// @param s The current reserve state.
/// @return mintAmt The amount of LP tokens minted.
function computeMintAmtWithPerp(
uint256 perpAmtIn,
ReserveState memory s
) public view returns (uint256) {
if (perpAmtIn <= 0) {
return 0;
}

uint256 valueIn = s.perpPrice.mulDiv(perpAmtIn, perpUnitAmt);
uint256 totalReserveVal = (s.usdPrice.mulDiv(s.usdBalance, usdUnitAmt) +
s.perpPrice.mulDiv(s.perpBalance, perpUnitAmt));

return
(totalReserveVal > 0)
? valueIn.mulDiv(totalSupply(), totalReserveVal).mulDiv(
ONE - fees.mintFeePerc,
ONE
)
: 0;
}

/// @notice Computes the amount of usd and perp tokens redeemed,
/// when the given number of LP tokens are burnt.
/// @param burnAmt The amount of LP tokens to be burnt.
Expand Down
1 change: 1 addition & 0 deletions spot-vaults/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export default {
enabled: !!process.env.REPORT_GAS,
excludeContracts: ["_test/"],
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
L1Etherscan: process.env.ETHERSCAN_API_KEY,
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
Expand Down
2 changes: 1 addition & 1 deletion spot-vaults/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"ethers": "^6.6.0",
"ethers-v5": "npm:ethers@^5.7.0",
"ganache-cli": "latest",
"hardhat": "^2.22.6",
"hardhat": "^2.22.8",
"hardhat-gas-reporter": "latest",
"lodash": "^4.17.21",
"prettier": "^2.7.1",
Expand Down
Loading