Skip to content

Commit

Permalink
feat: add serialize() to plugin contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
lekhovitsky committed Feb 9, 2025
1 parent b5d572d commit 41f8f77
Show file tree
Hide file tree
Showing 24 changed files with 91 additions and 12 deletions.
3 changes: 3 additions & 0 deletions contracts/core/DefaultAccountFactoryV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ contract DefaultAccountFactoryV3 is IDefaultAccountFactoryV3, Ownable {
);
}

/// @notice Empty state serialization
function serialize() external view override returns (bytes memory) {}

/// @notice Provides a reusable credit account from the queue to the credit manager.
/// If there are no accounts that can be reused in the queue, deploys a new one.
/// @return creditAccount Address of the provided credit account
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
pragma solidity ^0.8.17;

import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";

/// @title Account factory interface
/// @notice Generic interface for an account factory that can be used by credit managers to create credit accounts
/// @dev Account factories must have type `ACCOUNT_FACTORY::{POSTFIX}`
interface IAccountFactory is IVersion {
interface IAccountFactory is IVersion, IStateSerializer {
/// @notice Takes `creditAccount` from the account factory
/// @dev Parameters are kept for backward compatibility
function takeCreditAccount(uint256, uint256) external returns (address creditAccount);
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
pragma solidity ^0.8.17;

import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";

/// @title Adapter interface
/// @notice Generic interface for an adapter that can be used to interact with external protocols.
/// Adapters can be assumed to be non-malicious since they are developed by Gearbox DAO.
/// @dev Adapters must have type `ADAPTER::{POSTFIX}`
interface IAdapter is IVersion {
interface IAdapter is IVersion, IStateSerializer {
/// @notice Credit manager this adapter is connected to
/// @dev Assumed to be an immutable state variable
function creditManager() external view returns (address);
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IBot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
pragma solidity ^0.8.17;

import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";

/// @title Bot interface
/// @notice Minimal interface contracts must conform to in order to be used as bots in Gearbox V3
/// @dev Bots must have type `BOT::{POSTFIX}`
interface IBot is IVersion {
interface IBot is IVersion, IStateSerializer {
/// @notice Mask of permissions required for bot operation, see `ICreditFacadeV3Multicall`
function requiredPermissions() external view returns (uint192);
}
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IDegenNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
pragma solidity ^0.8.17;

import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";

/// @title Degen NFT interface
/// @notice Generic interface for a Degen NFT contract that can be used to restrict
/// non-whitelisted users from opening accounts through the credit facade
/// @dev Degen NFTs must have type `DEGEN_NFT::{POSTFIX}`
interface IDegenNFT is IVersion {
interface IDegenNFT is IVersion, IStateSerializer {
function burn(address from, uint256 amount) external;
}
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IInterestRateModel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
pragma solidity ^0.8.17;

import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";

/// @title Interest rate model interface
/// @notice Generic interface for an interest rate model contract that can be used in a pool
/// @dev Interest rate models must have type `IRM::{POSTFIX}`
interface IInterestRateModel is IVersion {
interface IInterestRateModel is IVersion, IStateSerializer {
/// @notice Calculates borrow rate based on utilization
/// @dev The last parameter can be used to prevent borrowing above maximum allowed utilization
/// @dev This function can be state-changing in case the IRM is stateful
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/base/ILossPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
pragma solidity ^0.8.17;

import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";

/// @title Loss policy interface
/// @notice Generic interface for a loss policy contract that dictates conditions under which a liquidation with bad debt
/// can proceed. For example, it can restrict such liquidations to only be performed by whitelisted accounts that
/// can return premium to the DAO to recover part of the losses, or prevent liquidations of an asset whose market
/// price drops for a short period of time while its fundamental value doesn't change.
/// @dev Loss policies must have type `LOSS_POLICY::{POSTFIX}`
interface ILossPolicy is IVersion {
interface ILossPolicy is IVersion, IStateSerializer {
/// @notice Whether `creditAccount` can be liquidated with loss by `caller`, `data` is an optional field
/// that can be used to pass some off-chain data specific to the loss policy implementation
function isLiquidatable(address creditAccount, address caller, bytes calldata data) external returns (bool);
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
pragma solidity ^0.8.17;

import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";

/// @title Price feed interface
/// @notice Interface for Chainlink-like price feeds that can be plugged into Gearbox's price oracle
/// @dev Price feeds must have type `PRICE_FEED::{POSTFIX}`
interface IPriceFeed is IVersion {
interface IPriceFeed is IVersion, IStateSerializer {
/// @notice Whether price feed implements its own staleness and sanity checks
function skipPriceCheck() external view returns (bool);

Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IRateKeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

import {IStateSerializer} from "./IStateSerializer.sol";
import {IVersion} from "./IVersion.sol";

/// @title Rate keeper interface
/// @notice Generic interface for a contract that can provide rates to the quota keeper
/// @dev Bots must have type `RATE_KEEPER::{POSTFIX}`
interface IRateKeeper is IVersion {
interface IRateKeeper is IVersion, IStateSerializer {
/// @notice Pool rates are provided for
function pool() external view returns (address);

Expand Down
11 changes: 11 additions & 0 deletions contracts/interfaces/base/IStateSerializer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

/// @title State serializer interface
/// @notice Generic interface for a contract that can serialize its state into a bytes array
interface IStateSerializer {
/// @notice Serializes the state of the contract into a bytes array `serializedData`
function serialize() external view returns (bytes memory serializedData);
}
3 changes: 2 additions & 1 deletion contracts/interfaces/base/IZapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

import {IStateSerializer} from "./IStateSerializer.sol";
import {IVersion} from "./IVersion.sol";

/// @title Zapper interface
/// @notice Generic interface for a zapper contract contract that can be used to perform complex batched
/// deposits into a pool which can, e.g., involve native token wrapping or staking pool's shares
/// @dev Zappers must have contract type `ZAPPER::{POSTFIX}`
interface IZapper is IVersion {
interface IZapper is IVersion, IStateSerializer {
/// @notice Pool a zapper deposits into
function pool() external view returns (address);

Expand Down
12 changes: 11 additions & 1 deletion contracts/pool/GaugeV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
pragma abicoder v1;

// INTERFACES
import {IGaugeV3, QuotaRateParams, UserVotes} from "../interfaces/IGaugeV3.sol";
Expand Down Expand Up @@ -80,6 +79,17 @@ contract GaugeV3 is IGaugeV3, ACLTrait, SanityCheckTrait {
emit SetFrozenEpoch(true); // U:[GA-1]
}

/// @notice Returns serialized gauge state
function serialize() external view override returns (bytes memory) {
address[] memory tokens = _poolQuotaKeeper().quotedTokens();
uint256 numTokens = tokens.length;
QuotaRateParams[] memory tokenParams = new QuotaRateParams[](numTokens);
for (uint256 i; i < numTokens; ++i) {
tokenParams[i] = quotaRateParams[tokens[i]];
}
return abi.encode(voter, epochLastUpdate, epochFrozen, tokens, tokenParams); // U:[GA-1]
}

/// @notice Updates the epoch and, unless frozen, rates in the quota keeper
function updateEpoch() external override {
_checkAndUpdateEpoch(); // U:[GA-14]
Expand Down
10 changes: 9 additions & 1 deletion contracts/pool/LinearInterestRateModelV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ contract LinearInterestRateModelV3 is ILinearInterestRateModelV3 {
isBorrowingMoreU2Forbidden = _isBorrowingMoreU2Forbidden; // U:[LIM-1]
}

/// @notice Returns serialized IRM parameters
function serialize() external view override returns (bytes memory) {
(uint16 U_1, uint16 U_2, uint16 R_base, uint16 R_slope1, uint16 R_slope2, uint16 R_slope3) =
getModelParameters();

return abi.encode(U_1, U_2, R_base, R_slope1, R_slope2, R_slope3, isBorrowingMoreU2Forbidden); // U:[LIM-1]
}

/// @notice Returns the borrow rate calculated based on expected and available liquidity
/// @param expectedLiquidity Expected liquidity in the pool
/// @param availableLiquidity Available liquidity in the pool
Expand Down Expand Up @@ -135,7 +143,7 @@ contract LinearInterestRateModelV3 is ILinearInterestRateModelV3 {

/// @notice Returns the model's parameters in basis points
function getModelParameters()
external
public
view
override
returns (uint16 U_1, uint16 U_2, uint16 R_base, uint16 R_slope1, uint16 R_slope2, uint16 R_slope3)
Expand Down
12 changes: 12 additions & 0 deletions contracts/pool/TumblerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ contract TumblerV3 is ITumblerV3, ACLTrait, SanityCheckTrait {
epochLength = epochLength_;
}

/// @notice Returns serialized tumbler state
/// @custom:tests U:[TU-1]
function serialize() external view override returns (bytes memory) {
address[] memory tokens = _tokensSet.values();
uint256 numTokens = tokens.length;
uint16[] memory rates = new uint16[](numTokens);
for (uint256 i; i < numTokens; ++i) {
rates[i] = _rates[tokens[i]];
}
return abi.encode(epochLength, tokens, rates);
}

/// @notice Whether `token` is added
/// @custom:tests U:[TU-2]
function isTokenAdded(address token) external view override returns (bool) {
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/mocks/core/AccountFactoryMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ contract AccountFactoryMock is Test, IAccountFactory {
version = _version;
}

function serialize() external view override returns (bytes memory) {}

/// @dev Provides a new credit account to a Credit Manager
/// @return creditAccount Address of credit account
function takeCreditAccount(uint256, uint256) external view override returns (address creditAccount) {
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/mocks/core/AdapterMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ contract AdapterMock is IAdapter {
targetContract = _targetContract;
}

function serialize() external view override returns (bytes memory) {}

function dumbCall() external returns (bool) {
_execute(dumbCallData());
return _return_useSafePrices;
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/mocks/core/BotMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ contract BotMock is IBot {
bytes32 public constant override contractType = "BOT::MOCK";
uint192 public override requiredPermissions;

function serialize() external view override returns (bytes memory) {}

function setRequiredPermissions(uint192 permissions) external {
requiredPermissions = permissions;
}
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/mocks/core/LossPolicyMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ contract LossPolicyMock is ILossPolicy {

bool public enabled = true;

function serialize() external view override returns (bytes memory) {}

function isLiquidatable(address, address, bytes calldata) external view override returns (bool) {
return enabled;
}
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/mocks/oracles/PriceFeedMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ contract PriceFeedMock is IPriceFeed {
updatedAt = block.timestamp + 36500 days;
}

function serialize() external view override returns (bytes memory) {}

function setParams(uint80 _roundId, uint256 _startedAt, uint256 _updatedAt, uint80 _answerInRound) external {
roundId = _roundId;
startedAt = _startedAt;
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/mocks/token/DegenNFTMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ contract DegenNFTMock is ERC721, IDegenNFT {
minter = msg.sender;
}

function serialize() external view override returns (bytes memory) {}

function mint(address to, uint256 amount) external {
uint256 balanceBefore = balanceOf(to);
for (uint256 i; i < amount; ++i) {
Expand Down
4 changes: 3 additions & 1 deletion contracts/test/mocks/token/PhantomTokenMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract PhantomTokenMock is IPhantomToken, ERC20 {

contract PhantomTokenWithdrawerMock is IAdapter, IPhantomTokenWithdrawer {
uint256 public constant override version = 3_10;
bytes32 public constant override contractType = "PHANTOM_TOKEN_WITHDRAWER::MOCK";
bytes32 public constant override contractType = "ADAPTER::PT_WITHDRAWER_MOCK";

address public immutable override creditManager;
address public immutable override targetContract;
Expand All @@ -64,6 +64,8 @@ contract PhantomTokenWithdrawerMock is IAdapter, IPhantomTokenWithdrawer {
(targetContract, depositedToken) = IPhantomToken(phantomToken_).getPhantomTokenInfo();
}

function serialize() external view override returns (bytes memory) {}

function withdrawPhantomToken(address, uint256 amount) external override returns (bool) {
address creditAccount = ICreditManagerV3(creditManager).getActiveCreditAccountOrRevert();
PhantomTokenMock(phantomToken).burn(creditAccount, amount);
Expand Down
8 changes: 8 additions & 0 deletions contracts/test/unit/pool/GaugeV3.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ contract GauageV3UnitTest is TestHelper, IGaugeV3Events {
poolMock = new PoolMock(address(addressProvider), underlying);

poolQuotaKeeperMock = address(new GeneralMock());
vm.mockCall(
poolQuotaKeeperMock, abi.encodeCall(IPoolQuotaKeeperV3.quotedTokens, ()), abi.encode(new address[](0))
);
poolMock.setPoolQuotaKeeper(poolQuotaKeeperMock);

gearStakingMock = new GearStakingMock();
Expand All @@ -72,6 +75,11 @@ contract GauageV3UnitTest is TestHelper, IGaugeV3Events {
assertEq(gauge.voter(), address(gearStakingMock), "Incorrect voter");
assertEq(gauge.epochLastUpdate(), 900, "Incorrect epoch");
assertTrue(gauge.epochFrozen(), "Epoch not frozen");
assertEq(
gauge.serialize(),
abi.encode(address(gearStakingMock), 900, true, new address[](0), new address[](0)),
"Incorrect serialized state"
);

vm.expectRevert(ZeroAddressException.selector);
new GaugeV3Harness(address(poolMock), address(0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ contract LinearInterestRateModelV3UnitTest is TestHelper {
assertEq(R_slope2, 3000);
assertEq(R_slope3, 4000);
assertTrue(irm.isBorrowingMoreU2Forbidden());
assertEq(irm.serialize(), abi.encode(8000, 9500, 1000, 2000, 3000, 4000, true), "Incorrect serialized state");
}

struct IncorrectParamCase {
Expand Down
3 changes: 3 additions & 0 deletions contracts/test/unit/pool/TumblerV3.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {
assertEq(tumbler.poolQuotaKeeper(), address(poolQuotaKeeper), "Incorrect poolQuotaKeeper");
assertEq(tumbler.epochLength(), 1 days, "Incorrect epochLength");
assertEq(tumbler.getTokens().length, 0, "Non-empty quoted tokens set");
assertEq(
tumbler.serialize(), abi.encode(1 days, new address[](0), new address[](0)), "Incorrect serialized state"
);
}

/// @notice U:[TU-2]: `addToken` works as expected
Expand Down

0 comments on commit 41f8f77

Please sign in to comment.