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

Revenue Facet #1182

Merged
merged 14 commits into from
Aug 9, 2024
102 changes: 9 additions & 93 deletions contracts/facade/facets/ActFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "@openzeppelin/contracts/utils/Multicall.sol";
import "../../plugins/trading/DutchTrade.sol";
import "../../plugins/trading/GnosisTrade.sol";
import "../../interfaces/IBackingManager.sol";
import "../lib/FacetLib.sol";

/**
* @title ActFacet
Expand Down Expand Up @@ -45,7 +46,7 @@ contract ActFacet is Multicall {
) external {
// Settle auctions
for (uint256 i = 0; i < toSettle.length; ++i) {
_settleTrade(revenueTrader, toSettle[i]);
FacetLib.settleTrade(revenueTrader, toSettle[i]);
}

// if 2.1.0, distribute tokenToBuy
Expand All @@ -59,10 +60,10 @@ contract ActFacet is Multicall {
if (toStart.length == 0) return;

// Transfer revenue backingManager -> revenueTrader
_forwardRevenue(revenueTrader.main().backingManager(), toStart);
FacetLib.forwardRevenue(revenueTrader.main().backingManager(), toStart);

// Start RevenueTrader auctions
_runRevenueAuctions(revenueTrader, toStart, kinds);
FacetLib.runRevenueAuctions(revenueTrader, toStart, kinds);
}

// === Static Calls ===
Expand Down Expand Up @@ -93,7 +94,7 @@ contract ActFacet is Multicall {
Registry memory reg = revenueTrader.main().assetRegistry().getRegistry();

// Forward ALL revenue
_forwardRevenue(bm, reg.erc20s);
FacetLib.forwardRevenue(bm, reg.erc20s);

erc20s = new IERC20[](reg.erc20s.length);
canStart = new bool[](reg.erc20s.length);
Expand All @@ -109,7 +110,7 @@ contract ActFacet is Multicall {
// Settle first if possible. Required so we can assess full available balance
ITrade trade = revenueTrader.trades(erc20s[i]);
if (address(trade) != address(0) && trade.canSettle()) {
_settleTrade(revenueTrader, erc20s[i]);
FacetLib.settleTrade(revenueTrader, erc20s[i]);
}

surpluses[i] = erc20s[i].balanceOf(address(revenueTrader));
Expand Down Expand Up @@ -175,15 +176,15 @@ contract ActFacet is Multicall {
for (uint256 i = 0; i < erc20s.length; ++i) {
ITrade trade = bm.trades(erc20s[i]);
if (address(trade) != address(0) && trade.canSettle()) {
_settleTrade(bm, erc20s[i]);
FacetLib.settleTrade(bm, erc20s[i]);
break; // backingManager can only have 1 trade open at a time
}
}
}

// If no auctions ongoing, to find a new auction to start
if (bm.tradesOpen() == 0) {
_rebalance(bm, kind);
FacetLib.rebalance(bm, kind);

// Find the started auction
for (uint256 i = 0; i < erc20s.length; ++i) {
Expand All @@ -192,95 +193,10 @@ contract ActFacet is Multicall {
canStart = true;
sell = trade.sell();
buy = trade.buy();
sellAmount = _getSellAmount(trade);
sellAmount = FacetLib.getSellAmount(trade);
}
}
}
}

// === Private ===
function _getSellAmount(ITrade trade) private view returns (uint256) {
if (trade.KIND() == TradeKind.DUTCH_AUCTION) {
return
DutchTrade(address(trade)).sellAmount().shiftl_toUint(
int8(trade.sell().decimals())
);
} else if (trade.KIND() == TradeKind.BATCH_AUCTION) {
return GnosisTrade(address(trade)).initBal();
} else {
revert("invalid trade type");
}
}

function _settleTrade(ITrading trader, IERC20 toSettle) private {
bytes1 majorVersion = bytes(trader.version())[0];
if (majorVersion == bytes1("3")) {
// Settle auctions
trader.settleTrade(toSettle);
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
address(trader).functionCall(abi.encodeWithSignature("settleTrade(address)", toSettle));
} else {
_revertUnrecognizedVersion();
}
}

function _forwardRevenue(IBackingManager bm, IERC20[] memory toStart) private {
bytes1 majorVersion = bytes(bm.version())[0];
// Need to use try-catch here in order to still show revenueOverview when basket not ready
if (majorVersion == bytes1("3")) {
// solhint-disable-next-line no-empty-blocks
try bm.forwardRevenue(toStart) {} catch {}
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(bm).call{ value: 0 }(
abi.encodeWithSignature("manageTokens(address[])", toStart)
);
success = success; // hush warning
} else {
_revertUnrecognizedVersion();
}
}

function _runRevenueAuctions(
IRevenueTrader revenueTrader,
IERC20[] memory toStart,
TradeKind[] memory kinds
) private {
bytes1 majorVersion = bytes(revenueTrader.version())[0];

if (majorVersion == bytes1("3")) {
revenueTrader.manageTokens(toStart, kinds);
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
for (uint256 i = 0; i < toStart.length; ++i) {
address(revenueTrader).functionCall(
abi.encodeWithSignature("manageToken(address)", toStart[i])
);
}
} else {
_revertUnrecognizedVersion();
}
}

function _rebalance(IBackingManager bm, TradeKind kind) private {
bytes1 majorVersion = bytes(bm.version())[0];

if (majorVersion == bytes1("3")) {
// solhint-disable-next-line no-empty-blocks
try bm.rebalance(kind) {} catch {}
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
IERC20[] memory emptyERC20s = new IERC20[](0);
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(bm).call{ value: 0 }(
abi.encodeWithSignature("manageTokens(address[])", emptyERC20s)
);
success = success; // hush warning
} else {
_revertUnrecognizedVersion();
}
}

function _revertUnrecognizedVersion() private pure {
revert("unrecognized version");
}
}
// slither-disable-end
1 change: 0 additions & 1 deletion contracts/facade/facets/ReadFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import "../../libraries/Fixed.sol";
import "../../p1/BasketHandler.sol";
import "../../p1/RToken.sol";
import "../../p1/StRSRVotes.sol";
import "./MaxIssuableFacet.sol";

/**
* @title ReadFacet
Expand Down
74 changes: 74 additions & 0 deletions contracts/facade/facets/RevenueFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 "../lib/FacetLib.sol";

/**
* @title AuctionsFacet
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably need to rename to RevenueFacet?

* @notice Single-function facet to return all revenues accumulating across RTokens
* @custom:static-call - Use ethers callStatic() to get result after update; do not execute
*/
// slither-disable-start
contract RevenueFacet {
struct Revenue {
IRToken rToken;
IERC20 erc20;
uint256 surplus;
}

// normally we don't let facets use storage, but this function requires it
// we don't yet have a full solution for sharding storage across facets
tbrent marked this conversation as resolved.
Show resolved Hide resolved
// so for now we'll just have to live with a janky hardcoded gap.
uint256[300] private __gap;
Revenue[] _revenues; // empty at-rest

Check warning on line 28 in contracts/facade/facets/RevenueFacet.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

Explicitly mark visibility of state

/// Return revenues across multiple RTokens
function revenues(IRToken[] memory rTokens)
external
returns (Revenue[] memory rTokenRevenues, Revenue[] memory rsrRevenues)
{
for (uint256 i = 0; i < rTokens.length; ++i) {
IMain main = rTokens[i].main();
IERC20[] memory erc20s = main.assetRegistry().erc20s();

// Forward ALL revenue
FacetLib.forwardRevenue(main.backingManager(), erc20s);

IRevenueTrader rTokenTrader = main.rTokenTrader();
IRevenueTrader rsrTrader = main.rsrTrader();
for (uint256 j = 0; j < erc20s.length; ++j) {
IERC20 erc20 = erc20s[j];

// RTokenTrader -- Settle first if possible so have full available balances
ITrade trade = rTokenTrader.trades(erc20);
if (address(trade) != address(0) && trade.canSettle()) {
FacetLib.settleTrade(rTokenTrader, erc20);
}
_revenues.push(Revenue(rTokens[i], erc20, erc20.balanceOf(address(rTokenTrader))));

// RSRTrader -- Settle first if possible so have full available balances
trade = rsrTrader.trades(erc20);
if (address(trade) != address(0) && trade.canSettle()) {
FacetLib.settleTrade(rsrTrader, erc20);
}
_revenues.push(Revenue(rTokens[i], erc20, erc20.balanceOf(address(rsrTrader))));
}
}

// Empty storage queue in reverse order, we know evens are RSR revenues and odds are RToken
rTokenRevenues = new Revenue[](_revenues.length / 2);
rsrRevenues = new Revenue[](_revenues.length / 2);
for (uint256 i = _revenues.length; i > 0; --i) {
if (i % 2 == 0) rsrRevenues[(i - 1) / 2] = _revenues[i - 1];
else rTokenRevenues[(i - 1) / 2] = _revenues[i - 1];
_revenues.pop();
}
assert(_revenues.length == 0);
}
}
// slither-disable-end
100 changes: 100 additions & 0 deletions contracts/facade/lib/FacetLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "@openzeppelin/contracts/utils/Address.sol";
import "../../interfaces/IBackingManager.sol";
import "../../interfaces/IRevenueTrader.sol";
import "../../interfaces/ITrade.sol";
import "../../interfaces/ITrading.sol";
import "../../plugins/trading/DutchTrade.sol";
import "../../plugins/trading/GnosisTrade.sol";
import "../../libraries/Fixed.sol";

library FacetLib {
using Address for address;
using FixLib for uint192;

function getSellAmount(ITrade trade) internal view returns (uint256) {
if (trade.KIND() == TradeKind.DUTCH_AUCTION) {
return
DutchTrade(address(trade)).sellAmount().shiftl_toUint(
int8(trade.sell().decimals())
);
} else if (trade.KIND() == TradeKind.BATCH_AUCTION) {
return GnosisTrade(address(trade)).initBal();
} else {
revert("invalid trade type");
}
}

function settleTrade(ITrading trader, IERC20 toSettle) internal {
bytes1 majorVersion = bytes(trader.version())[0];
if (majorVersion == bytes1("3")) {
// Settle auctions
trader.settleTrade(toSettle);
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
address(trader).functionCall(abi.encodeWithSignature("settleTrade(address)", toSettle));
} else {
_revertUnrecognizedVersion();
}
}

function forwardRevenue(IBackingManager bm, IERC20[] memory toStart) internal {
bytes1 majorVersion = bytes(bm.version())[0];
// Need to use try-catch here in order to still show revenueOverview when basket not ready
if (majorVersion == bytes1("3")) {
// solhint-disable-next-line no-empty-blocks
try bm.forwardRevenue(toStart) {} catch {}
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(bm).call{ value: 0 }(
abi.encodeWithSignature("manageTokens(address[])", toStart)
);
success = success; // hush warning
} else {
_revertUnrecognizedVersion();
}
}

function runRevenueAuctions(
IRevenueTrader revenueTrader,
IERC20[] memory toStart,
TradeKind[] memory kinds
) internal {
bytes1 majorVersion = bytes(revenueTrader.version())[0];

if (majorVersion == bytes1("3")) {
revenueTrader.manageTokens(toStart, kinds);
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
for (uint256 i = 0; i < toStart.length; ++i) {
address(revenueTrader).functionCall(
abi.encodeWithSignature("manageToken(address)", toStart[i])
);
}
} else {
_revertUnrecognizedVersion();
}
}

function rebalance(IBackingManager bm, TradeKind kind) internal {
bytes1 majorVersion = bytes(bm.version())[0];

if (majorVersion == bytes1("3")) {
// solhint-disable-next-line no-empty-blocks
try bm.rebalance(kind) {} catch {}
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
IERC20[] memory emptyERC20s = new IERC20[](0);
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(bm).call{ value: 0 }(
abi.encodeWithSignature("manageTokens(address[])", emptyERC20s)
);
success = success; // hush warning
} else {
_revertUnrecognizedVersion();
}
}

function _revertUnrecognizedVersion() internal pure {
revert("unrecognized version");
}
}
4 changes: 3 additions & 1 deletion contracts/interfaces/IFacade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "../facade/facets/ActFacet.sol";
import "../facade/facets/ReadFacet.sol";
import "../facade/facets/BackingBufferFacet.sol";
import "../facade/facets/MaxIssuableFacet.sol";
import "../facade/facets/RevenueFacet.sol";

interface IFacade {
event SelectorSaved(address indexed facet, bytes4 indexed selector);
Expand All @@ -21,7 +22,8 @@ abstract contract TestIFacade is
ActFacet,
BackingBufferFacet,
MaxIssuableFacet,
ReadFacet
ReadFacet,
RevenueFacet
{

}
3 changes: 2 additions & 1 deletion scripts/addresses/1-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"actFacet": "0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB",
"readFacet": "0x823110a13eB26cB09c4Bb118DBfE4ff5f96D5526",
"maxIssuableFacet": "0x5771d976696AA180Fed276FB6571fE2f41D0b849",
"backingBufferFacet": "0xB555921a031D321687aE8B0569dA7B6da8BCB209"
"backingBufferFacet": "0xB555921a031D321687aE8B0569dA7B6da8BCB209",
"revenueFacet": "0xc104A65a1DA673FdC61f6aEe7320CAC490c2814d"
},
"facadeWriteLib": "0xDf73Cd789422040182b0C24a8b2C97bbCbba3263",
"basketLib": "0xf383dC60D29A5B9ba461F40A0606870d80d1EA88",
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 @@ -10,7 +10,8 @@
"actFacet": "0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB",
"readFacet": "0x823110a13eB26cB09c4Bb118DBfE4ff5f96D5526",
"maxIssuableFacet": "0x5771d976696AA180Fed276FB6571fE2f41D0b849",
"backingBufferFacet": "0xB555921a031D321687aE8B0569dA7B6da8BCB209"
"backingBufferFacet": "0xB555921a031D321687aE8B0569dA7B6da8BCB209",
"revenueFacet": "0xc104A65a1DA673FdC61f6aEe7320CAC490c2814d"
},
"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 @@ -45,7 +45,8 @@ async function main() {
'phase1-facade/1_deploy_readFacet.ts',
'phase1-facade/2_deploy_actFacet.ts',
'phase1-facade/3_deploy_maxIssuable.ts',
'phase1-facade/4_deploy_backingBufferFacet.ts'
'phase1-facade/4_deploy_backingBufferFacet.ts',
'phase1-facade/5_deploy_revenueFacet.ts'
)

// =============================================
Expand Down
Loading
Loading