Skip to content

Commit

Permalink
Backing buffer facet (#1180)
Browse files Browse the repository at this point in the history
Co-authored-by: Julian R <[email protected]>
  • Loading branch information
tbrent and julianmrodri authored Aug 4, 2024
1 parent d7be553 commit 5b428cd
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 8 deletions.
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

0 comments on commit 5b428cd

Please sign in to comment.