Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backing buffer facet #1180

Merged
merged 6 commits into from
Aug 4, 2024
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
68 changes: 68 additions & 0 deletions contracts/facade/facets/BackingBufferFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../interfaces/IAssetRegistry.sol";
import "../../interfaces/IBackingManager.sol";
import "../../interfaces/IBasketHandler.sol";
import "../../interfaces/IRToken.sol";
import "../../libraries/Fixed.sol";

/**
* @title BackingBufferFacet
* @notice Single-function facet for the Facade to compute the backing buffer % filled
* @custom:static-call - Use ethers callStatic() to get result after update; do not execute
*/
// slither-disable-start
contract BackingBufferFacet {
using FixLib for uint192;

// === Static Calls ===

/// @return required {UoA} The required USD value of the backing buffer at current market caps
/// @return actual {UoA} The actual USD value of the excess balances of the backing buffer
/// @custom:static-call
function backingBuffer(IRToken rToken) external returns (uint192 required, uint192 actual) {
IMain main = rToken.main();
IAssetRegistry reg = main.assetRegistry();
IBasketHandler bh = main.basketHandler();
TestIBackingManager bm = TestIBackingManager(address(main.backingManager()));

// Refresh AssetRegistry
reg.refresh();

// Read RToken state
uint192 buffer = bm.backingBuffer(); // {1}
uint192 basketsNeeded = rToken.basketsNeeded(); // {BU}
uint192 buAvgPrice = _getAvgPrice(IAsset(address(bh))); // {UoA/BU}

// Calculate `required`
uint192 basketsInBuffer = basketsNeeded.mul(FIX_ONE + buffer, CEIL).minus(basketsNeeded);
// {UoA} = {UoA/BU} * {BU}
required = buAvgPrice.mul(basketsInBuffer, CEIL);

// Calculate `actual`
(address[] memory erc20s, ) = bh.quote(FIX_ONE, FLOOR);
for (uint256 i = 0; i < erc20s.length; ++i) {
IAsset asset = reg.toAsset(IERC20(erc20s[i]));

// {tok} = {BU} * {tok/BU}
uint192 req = basketsNeeded.mul(bh.quantity(IERC20(erc20s[i])), CEIL);
uint192 bal = asset.bal(address(bm));

// {UoA} = ({qTok} - {qTok}) * {UoA/tok}
if (bal.gt(req)) actual += bal.minus(req).mul(_getAvgPrice(asset), CEIL);
}
}

/// === Private ===

/// Works for IBasketHandler too
/// @return {UoA/tok} or {UoA/BU} The average price of the asset or BasketHandler, in UoA
function _getAvgPrice(IAsset asset) private view returns (uint192) {
(uint192 low, uint192 high) = asset.price(); // will include issuance premium in 4.0.0
if (low == 0 || high == FIX_MAX) return 0;
return (low + high) / 2; // {UoA/tok}
}
}
// slither-disable-end
9 changes: 8 additions & 1 deletion contracts/interfaces/IFacade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.19;

import "../facade/facets/ActFacet.sol";
import "../facade/facets/ReadFacet.sol";
import "../facade/facets/BackingBufferFacet.sol";
import "../facade/facets/MaxIssuableFacet.sol";

interface IFacade {
Expand All @@ -15,6 +16,12 @@ interface IFacade {
}

// solhint-disable-next-line no-empty-blocks
abstract contract TestIFacade is IFacade, ActFacet, MaxIssuableFacet, ReadFacet {
abstract contract TestIFacade is
IFacade,
ActFacet,
BackingBufferFacet,
MaxIssuableFacet,
ReadFacet
{

}
5 changes: 3 additions & 2 deletions scripts/addresses/1-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"facets": {
"actFacet": "0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB",
"readFacet": "0x823110a13eB26cB09c4Bb118DBfE4ff5f96D5526",
"maxIssuableFacet": "0x5771d976696AA180Fed276FB6571fE2f41D0b849"
"maxIssuableFacet": "0x5771d976696AA180Fed276FB6571fE2f41D0b849",
"backingBufferFacet": "0xB555921a031D321687aE8B0569dA7B6da8BCB209"
},
"facadeWriteLib": "0xDf73Cd789422040182b0C24a8b2C97bbCbba3263",
"basketLib": "0xf383dC60D29A5B9ba461F40A0606870d80d1EA88",
Expand All @@ -35,4 +36,4 @@
"stRSR": "0xE433673648c94FEC0706E5AC95d4f4097f58B5fb"
}
}
}
}
6 changes: 4 additions & 2 deletions scripts/addresses/42161-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"facade": "0x387A0C36681A22F728ab54426356F4CAa6bB48a9",
"facets": {
"actFacet": "0xE774CCF1431c3DEe7Fa4c20f67534b61289CAa45",
"readFacet": "0x15175d35F3d88548B49600B4ee8067253A2e4e66"
"readFacet": "0x15175d35F3d88548B49600B4ee8067253A2e4e66",
"backingBufferFacet": "0x73094D84683d712E02f47eddEfF70A6EDf6D59eD",
"maxIssuableFacet": "0x09108763270A8EB0D0Ca30906FEC49fa0944BFE6"
},
"facadeWriteLib": "0x042D85e9eb1F4372ffA362240E0630229CaA1904",
"basketLib": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA",
Expand All @@ -35,4 +37,4 @@
"stRSR": "0x437b525F96A2Da0A4b165efe27c61bea5c8d3CD4"
}
}
}
}
28 changes: 28 additions & 0 deletions scripts/addresses/8453-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"assets": {
"COMP": "0xB8794Fb1CCd62bFe631293163F4A3fC2d22e37e0",
"STG": "0xEE527CC63122732532d0f1ad33Ec035D30f3050f"
},
"collateral": {
"DAI": "0x3E40840d0282C9F9cC7d17094b5239f87fcf18e5",
"USDC": "0xaa85216187F92a781D8F9Bcb40825E356ee2635a",
"USDbC": "0xD126741474B0348D9B0F4911573d8f543c01C2c4",
"WETH": "0x073BD162BBD05Cd2CF631B90D44239B8a367276e",
"cbETH": "0x851B461a9744f4c9E996C03072cAB6f44Fa04d0D",
"saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50",
"cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461",
"wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73"
},
"erc20s": {
"COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0",
"DAI": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
"USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"USDbC": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
"WETH": "0x4200000000000000000000000000000000000006",
"cbETH": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22",
"saBasUSDC": "0x6F6f81e5E66f503184f2202D83a79650c3285759",
"STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df",
"cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA",
"wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452"
}
}
39 changes: 39 additions & 0 deletions scripts/addresses/8453-tmp-deployments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"prerequisites": {
"RSR": "0xaB36452DbAC151bE02b16Ca17d8919826072f64a",
"RSR_FEED": "0xAa98aE504658766Dfe11F31c5D95a0bdcABDe0b1",
"GNOSIS_EASY_AUCTION": "0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02"
},
"tradingLib": "0x6419fe6cf428150e2d8ed38a3316b1bb468f79a7",
"facade": "0xEb2071e9B542555E90E6e4E1F83fa17423583991",
"facets": {
"actFacet": "0x0eac15B9Fe585432E48Cf175571D75D111861F43",
"readFacet": "0x5Af543D6F95a98200Dd770f39A902Fe793BAeB27",
"maxIssuableFacet": "0x63FDcB1E8Ee5C4B64A5c4ce0FB97597917920cb6",
"backingBufferFacet": "0x38c7e9427960E427f6c84b3A096021f47a9Afb82"
},
"facadeWriteLib": "0x186d05580E6B7195323b5dC8c3ee9179Ad086d4C",
"basketLib": "0x182e86ad4a6139ced4f9fa4ed3f1cd9e4f7449e7",
"facadeWrite": "0x43E205A805c4be5A62C71d49de68dF60200548A0",
"deployer": "0xFD18bA9B2f9241Ce40CDE14079c1cDA1502A8D0A",
"rsrAsset": "0x02062c16c28A169D1f2F5EfA7eEDc42c3311ec23",
"implementations": {
"main": "0x2a2A842Dda2Da2170a531dfF4bD4A821321e4485",
"trading": {
"gnosisTrade": "0x93de153Ba104D15785c8d8af01AE9425960de49e",
"dutchTrade": "0x270284ecb6aF0dc521D2c8f9D77b03EEd2aace90"
},
"components": {
"assetRegistry": "0x3DDe17cfd36e740CB7452cb2F59FC925eACb91aB",
"backingManager": "0xb5bDFF1FB47635383ABf13b78a79C8a21aA1b23E",
"basketHandler": "0xA4f1Fc88eFF9a72bCc278a2D3B79cafCc1551fb5",
"broker": "0x1cddc45cb390C3b4a739861155E8ee95b7321eD6",
"distributor": "0xba748FAF1a94B5C8De5C8Ca8D87A0906C5B0300c",
"furnace": "0xE0B810bD674132b553770064Fc90440c5A5f518d",
"rsrTrader": "0x3c2460ACa70bedf096f71Cf91fFBc0789F08503f",
"rTokenTrader": "0x3c2460ACa70bedf096f71Cf91fFBc0789F08503f",
"rToken": "0x02Ab5B6dF2c17d060EE3e95D08225Ff3A42504a5",
"stRSR": "0x4Cf200D7fA568611DD8B4BD85053ba9419982C7D"
}
}
}
3 changes: 2 additions & 1 deletion scripts/addresses/base-3.4.0/8453-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"facets": {
"actFacet": "0x0eac15B9Fe585432E48Cf175571D75D111861F43",
"readFacet": "0x5Af543D6F95a98200Dd770f39A902Fe793BAeB27",
"maxIssuableFacet": "0x63FDcB1E8Ee5C4B64A5c4ce0FB97597917920cb6"
"maxIssuableFacet": "0x63FDcB1E8Ee5C4B64A5c4ce0FB97597917920cb6",
"backingBufferFacet": "0x38c7e9427960E427f6c84b3A096021f47a9Afb82"
},
"facadeWriteLib": "0x186d05580E6B7195323b5dC8c3ee9179Ad086d4C",
"basketLib": "0x182e86ad4a6139ced4f9fa4ed3f1cd9e4f7449e7",
Expand Down
3 changes: 2 additions & 1 deletion scripts/addresses/mainnet-3.4.0/1-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"facets": {
"actFacet": "0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB",
"readFacet": "0x823110a13eB26cB09c4Bb118DBfE4ff5f96D5526",
"maxIssuableFacet": "0x5771d976696AA180Fed276FB6571fE2f41D0b849"
"maxIssuableFacet": "0x5771d976696AA180Fed276FB6571fE2f41D0b849",
"backingBufferFacet": "0xB555921a031D321687aE8B0569dA7B6da8BCB209"
},
"facadeWriteLib": "0xDf73Cd789422040182b0C24a8b2C97bbCbba3263",
"basketLib": "0xf383dC60D29A5B9ba461F40A0606870d80d1EA88",
Expand Down
3 changes: 2 additions & 1 deletion scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ async function main() {
scripts.push(
'phase1-facade/1_deploy_readFacet.ts',
'phase1-facade/2_deploy_actFacet.ts',
'phase1-facade/3_deploy_maxIssuable.ts'
'phase1-facade/3_deploy_maxIssuable.ts',
'phase1-facade/4_deploy_backingBufferFacet.ts'
)

// =============================================
Expand Down
1 change: 1 addition & 0 deletions scripts/deployment/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface IFacets {
readFacet: string
// individiual function facets
maxIssuableFacet: string
backingBufferFacet: string
}

export interface IDeployments {
Expand Down
67 changes: 67 additions & 0 deletions scripts/deployment/phase1-facade/4_deploy_backingBufferFacet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fs from 'fs'
import hre, { ethers } from 'hardhat'

import { getChainId, isValidContract } from '../../../common/blockchain-utils'
import { networkConfig } from '../../../common/configuration'
import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common'
import { BackingBufferFacet } from '../../../typechain'

let backingBufferFacet: BackingBufferFacet

async function main() {
// ==== Read Configuration ====
const [burner] = await hre.ethers.getSigners()
const chainId = await getChainId(hre)

console.log(`Deploying Facade to network ${hre.network.name} (${chainId})
with burner account: ${burner.address}`)

if (!networkConfig[chainId]) {
throw new Error(`Missing network configuration for ${hre.network.name}`)
}

const deploymentFilename = getDeploymentFilename(chainId)
const deployments = <IDeployments>getDeploymentFile(deploymentFilename)

// Check facade exists
if (!deployments.facade) {
throw new Error(`Missing deployed contracts in network ${hre.network.name}`)
} else if (!(await isValidContract(hre, deployments.facade))) {
throw new Error(`Facade contract not found in network ${hre.network.name}`)
}

// ******************** Deploy BackingBufferFacet ****************************************/

// Deploy BackingBufferFacet
const BackingBufferFacetFactory = await ethers.getContractFactory('BackingBufferFacet')
backingBufferFacet = <BackingBufferFacet>await BackingBufferFacetFactory.connect(burner).deploy()
await backingBufferFacet.deployed()

// Write temporary deployments file
deployments.facets.backingBufferFacet = backingBufferFacet.address
fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2))

console.log(`Deployed to ${hre.network.name} (${chainId})
BackingBufferFacet: ${backingBufferFacet.address}
Deployment file: ${deploymentFilename}`)

// ******************** Save to Facade ****************************************/

console.log('Configuring with Facade...')

// Save BackingBufferFacet functions to Facade
const facade = await ethers.getContractAt('Facade', deployments.facade)
await facade.save(
backingBufferFacet.address,
Object.entries(backingBufferFacet.functions).map(([fn]) =>
backingBufferFacet.interface.getSighash(fn)
)
)

console.log('Finished saving to Facade')
}

main().catch((error) => {
console.error(error)
process.exitCode = 1
})
8 changes: 8 additions & 0 deletions scripts/verification/4_verify_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ async function main() {
[],
'contracts/facade/facets/MaxIssuableFacet.sol:MaxIssuableFacet'
)

/** ******************** Verify BackingBufferFacet ****************************************/
await verifyContract(
chainId,
deployments.facets.backingBufferFacet,
[],
'contracts/facade/facets/BackingBufferFacet.sol:BackingBufferFacet'
)
}

main().catch((error) => {
Expand Down
22 changes: 22 additions & 0 deletions test/Facade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,28 @@ describe('Facade + FacadeMonitor contracts', () => {
expect(await facade.stToken(rToken.address)).to.equal(stRSR.address)
})

it('Should return backingBuffer', async () => {
let [required, actual] = await facade.callStatic.backingBuffer(rToken.address)
expect(required).to.be.closeTo(fp('0.01'), fp('0.0001'))
expect(actual).to.equal(0)

// Mimic 10% even appreciation across the board on a 0.01% backingBuffer
const [erc20s, amounts] = await basketHandler.quote(issueAmount, 0)
for (let i = 0; i < erc20s.length; i++) {
const erc20 = await ethers.getContractAt('ERC20Mock', erc20s[i])
await erc20.connect(addr1).mint(backingManager.address, amounts[i].div(10))
}
;[required, actual] = await facade.callStatic.backingBuffer(rToken.address)
expect(required).to.be.closeTo(fp('0.01'), fp('0.0001'))
expect(actual).to.equal(fp('10')) // 10%

// Add-in an uneven balance to get to 12.5% total appreciation on a 0.01% backingBuffer
await token.connect(addr1).mint(backingManager.address, issueAmount.div(4).div(10))
;[required, actual] = await facade.callStatic.backingBuffer(rToken.address)
expect(required).to.be.closeTo(fp('0.01'), fp('0.0001'))
expect(actual).to.equal(fp('12.5')) // 12.5%
})

it('Should return maxIssuable correctly', async () => {
// Regression test
// April 2nd 2024 -- maxIssuableByAmounts did not account for appreciation
Expand Down
15 changes: 15 additions & 0 deletions test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Asset,
AssetRegistryP1,
ATokenFiatCollateral,
BackingBufferFacet,
BackingManagerP1,
BasketHandlerP1,
BasketLibP1,
Expand Down Expand Up @@ -413,6 +414,7 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt
readFacet: ReadFacet
actFacet: ActFacet
maxIssuableFacet: MaxIssuableFacet
backingBufferFacet: BackingBufferFacet
facadeTest: FacadeTest
facadeMonitor: FacadeMonitor
broker: TestIBroker
Expand Down Expand Up @@ -765,6 +767,18 @@ const makeDefaultFixture = async (setBasket: boolean): Promise<DefaultFixture> =
)
)

// Save BackingBufferFacet to Facade
const BackingBufferFacetFactory: ContractFactory = await ethers.getContractFactory(
'BackingBufferFacet'
)
const backingBufferFacet = <BackingBufferFacet>await BackingBufferFacetFactory.deploy()
await facade.save(
backingBufferFacet.address,
Object.entries(backingBufferFacet.functions).map(([fn]) =>
backingBufferFacet.interface.getSighash(fn)
)
)

return {
rsr,
rsrAsset,
Expand Down Expand Up @@ -797,6 +811,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise<DefaultFixture> =
readFacet,
actFacet,
maxIssuableFacet,
backingBufferFacet,
facadeTest,
facadeMonitor,
rsrTrader,
Expand Down
Loading
Loading