Skip to content

Commit 42e7a19

Browse files
Rollover vault tests (#115)
* bumped up compiler version * using smock for contract mocks * vault unit tests * ran linter * using require instead of assert for init check * fixed underflow bug with array index * Debasement aware Fee strategy (#140) * fee strategy update * updated testcase text * removed single use variable * Apply suggestions from code review Co-authored-by: Brandon Iles <brandon@fragments.org> * code review fixes * Vault interface (#141) * standard vault interface * linter update --------- Co-authored-by: Brandon Iles <brandon@fragments.org> --------- Co-authored-by: Brandon Iles <brandon@fragments.org>
1 parent a3cde17 commit 42e7a19

38 files changed

+5216
-1450
lines changed

spot-contracts/.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module.exports = {
55
mocha: true,
66
node: true,
77
},
8-
plugins: ["@typescript-eslint"],
8+
plugins: ["@typescript-eslint", "no-only-tests"],
99
extends: ["standard", "plugin:prettier/recommended", "plugin:node/recommended"],
1010
parser: "@typescript-eslint/parser",
1111
parserOptions: {
@@ -26,5 +26,6 @@ module.exports = {
2626
allowModules: ["hardhat", "ethers", "@openzeppelin/upgrades-core"],
2727
},
2828
],
29+
"no-only-tests/no-only-tests": "error",
2930
},
3031
};

spot-contracts/contracts/BondIssuer.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
55
import { EnumerableSetUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";

spot-contracts/contracts/PerpetualTranche.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
55
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";

spot-contracts/contracts/RouterV1.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
55
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity ^0.8.0;
3+
4+
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
5+
6+
/// @notice Expected asset to be a valid vault asset.
7+
/// @param token Address of the token.
8+
error UnexpectedAsset(IERC20Upgradeable token);
9+
10+
/// @notice Expected transfer out asset to not be a vault asset.
11+
/// @param token Address of the token transferred.
12+
error UnauthorizedTransferOut(IERC20Upgradeable token);
13+
14+
/// @notice Expected vault assets to be deployed.
15+
error NoDeployment();
16+
17+
/*
18+
* @title IVault
19+
*
20+
* @notice The standard interface for a generic vault as described by the "Vault Framework".
21+
* http://thinking.farm/essays/2022-10-05-mechanical-finance/
22+
*
23+
* Users deposit a "underlying" asset and mint "notes" (or vault shares).
24+
* The vault "deploys" underlying asset in a rules-based fashion (through a hard-coded strategy)
25+
* to "earn" income. It "recovers" deployed assets once the investment matures.
26+
*
27+
* The vault operates through two external poke functions which off-chain keepers can execute.
28+
* 1) `deploy`: When executed, the vault "puts to work" the underlying assets it holds. The vault
29+
* usually returns other ERC-20 tokens which act as receipts of the deployment.
30+
* 2) `recover`: When executed, the vault turns in the receipts and retrieves the underlying asset and
31+
* usually collects some yield for this work.
32+
*
33+
* The rules of the deployment and recovery are specific to the vault strategy.
34+
*
35+
* At any time the vault will hold multiple ERC20 tokens, together referred to as the vault's "assets".
36+
* They can be a combination of the underlying asset, the earned asset and the deployed assets (receipts).
37+
*
38+
* On redemption users burn their "notes" to receive a proportional slice of all the vault's assets.
39+
*
40+
*/
41+
42+
interface IVault {
43+
/// @notice Recovers deployed funds and redeploys them.
44+
function recoverAndRedeploy() external;
45+
46+
/// @notice Deploys deposited funds.
47+
function deploy() external;
48+
49+
/// @notice Recovers deployed funds.
50+
function recover() external;
51+
52+
/// @notice Deposits the underlying asset from {msg.sender} into the vault and mints notes.
53+
/// @param amount The amount tokens to be deposited into the vault.
54+
/// @return The amount of notes.
55+
function deposit(uint256 amount) external returns (uint256);
56+
57+
struct TokenAmount {
58+
/// @notice The asset token redeemed.
59+
IERC20Upgradeable token;
60+
/// @notice The amount redeemed.
61+
uint256 amount;
62+
}
63+
64+
/// @notice Burns notes and sends a proportional share of vault's assets back to {msg.sender}.
65+
/// @param notes The amount of notes to be burnt.
66+
/// @return The list of asset tokens and amounts redeemed.
67+
function redeem(uint256 notes) external returns (TokenAmount[] memory);
68+
69+
/// @return The total value of assets currently held by the vault, denominated in a standard unit of account.
70+
function getTVL() external returns (uint256);
71+
72+
/// @notice The ERC20 token that can be deposited into this vault.
73+
function underlying() external view returns (IERC20Upgradeable);
74+
75+
/// @param token The address of the asset ERC-20 token held by the vault.
76+
/// @return The vault's asset token balance.
77+
function vaultAssetBalance(IERC20Upgradeable token) external view returns (uint256);
78+
79+
/// @return Total count of deployed asset tokens held by the vault.
80+
function deployedCount() external view returns (uint256);
81+
82+
/// @param i The index of a token.
83+
/// @return The token address from the deployed asset token list by index.
84+
function deployedAt(uint256 i) external view returns (IERC20Upgradeable);
85+
86+
/// @return Total count of earned income tokens held by the vault.
87+
function earnedCount() external pure returns (uint256);
88+
89+
/// @param i The index of a token.
90+
/// @return The token address from the earned income token list by index.
91+
function earnedAt(uint256 i) external view returns (IERC20Upgradeable);
92+
93+
/// @param token The address of a token to check.
94+
/// @return If the given token is held by the vault.
95+
function isVaultAsset(IERC20Upgradeable token) external view returns (bool);
96+
}

spot-contracts/contracts/_utils/BondHelpers.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
55
import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";

spot-contracts/contracts/_utils/SignedMathHelpers.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
/**
55
* @title SignedMathHelpers

spot-contracts/contracts/strategies/BasicFeeStrategy.sol

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
55
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
66
import { SignedMathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SignedMathUpgradeable.sol";
7+
import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
78
import { SignedMathHelpers } from "../_utils/SignedMathHelpers.sol";
89

910
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
@@ -26,8 +27,8 @@ error UnacceptablePercValue(int256 perc);
2627
contract BasicFeeStrategy is IFeeStrategy, OwnableUpgradeable {
2728
using SignedMathUpgradeable for int256;
2829
using SignedMathHelpers for int256;
29-
using SafeCastUpgradeable for uint256;
3030
using SafeCastUpgradeable for int256;
31+
using SafeCastUpgradeable for uint256;
3132

3233
/// @dev {10 ** PERC_DECIMALS} is considered 1%
3334
uint8 public constant PERC_DECIMALS = 6;
@@ -38,6 +39,10 @@ contract BasicFeeStrategy is IFeeStrategy, OwnableUpgradeable {
3839
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
3940
IERC20Upgradeable public immutable override feeToken;
4041

42+
/// @notice The address of the fee reserve.
43+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
44+
address public immutable feeReserve;
45+
4146
/// @notice Fixed percentage of the mint amount to be used as fee.
4247
int256 public mintFeePerc;
4348

@@ -47,6 +52,11 @@ contract BasicFeeStrategy is IFeeStrategy, OwnableUpgradeable {
4752
/// @notice Fixed percentage of the rollover amount to be used as fee.
4853
int256 public rolloverFeePerc;
4954

55+
/// @notice Allows debasement of perp supply when the fee reserve is empty.
56+
/// @dev When the fee amount is negative, ie) paid from the reserve to the user
57+
/// this flag stops paying out more than the reserve balance through perp supply inflation.
58+
bool public allowDebase;
59+
5060
// EVENTS
5161

5262
/// @notice Event emitted when the mint fee percentage is updated.
@@ -61,10 +71,16 @@ contract BasicFeeStrategy is IFeeStrategy, OwnableUpgradeable {
6171
/// @param rolloverFeePerc Rollover fee percentage.
6272
event UpdatedRolloverPerc(int256 rolloverFeePerc);
6373

74+
/// @notice Event emitted when the debasement rule is updated.
75+
/// @param allow If debasement is allowed or not.
76+
event UpdatedDebasementRule(bool allow);
77+
6478
/// @notice Contract constructor.
6579
/// @param feeToken_ Address of the fee ERC-20 token contract.
66-
constructor(IERC20Upgradeable feeToken_) {
80+
/// @param feeReserve_ Address of the fee reserve.
81+
constructor(IERC20Upgradeable feeToken_, address feeReserve_) {
6782
feeToken = feeToken_;
83+
feeReserve = feeReserve_;
6884
}
6985

7086
/// @notice Contract initializer.
@@ -103,21 +119,41 @@ contract BasicFeeStrategy is IFeeStrategy, OwnableUpgradeable {
103119
emit UpdatedRolloverPerc(rolloverFeePerc_);
104120
}
105121

122+
/// @notice Update debasement rule.
123+
/// @param allow If debasement is allowed or not.
124+
function allowDebasement(bool allow) public onlyOwner {
125+
allowDebase = allow;
126+
emit UpdatedDebasementRule(allow);
127+
}
128+
106129
/// @inheritdoc IFeeStrategy
107130
function computeMintFees(uint256 mintAmt) external view override returns (int256, uint256) {
108-
uint256 absoluteFee = (mintFeePerc.abs() * mintAmt) / HUNDRED_PERC;
109-
return (mintFeePerc.sign() * absoluteFee.toInt256(), 0);
131+
return (_computeFeeAmt(mintAmt, mintFeePerc), 0);
110132
}
111133

112134
/// @inheritdoc IFeeStrategy
113135
function computeBurnFees(uint256 burnAmt) external view override returns (int256, uint256) {
114-
uint256 absoluteFee = (burnFeePerc.abs() * burnAmt) / HUNDRED_PERC;
115-
return (burnFeePerc.sign() * absoluteFee.toInt256(), 0);
136+
return (_computeFeeAmt(burnAmt, burnFeePerc), 0);
116137
}
117138

118139
/// @inheritdoc IFeeStrategy
119140
function computeRolloverFees(uint256 rolloverAmt) external view override returns (int256, uint256) {
120-
uint256 absoluteFee = (rolloverFeePerc.abs() * rolloverAmt) / HUNDRED_PERC;
121-
return (rolloverFeePerc.sign() * absoluteFee.toInt256(), 0);
141+
return (_computeFeeAmt(rolloverAmt, rolloverFeePerc), 0);
142+
}
143+
144+
/// @dev Given the token amount and fee percentage, computes the integer amount to be charged as fees.
145+
function _computeFeeAmt(uint256 amount, int256 feePerc) private view returns (int256) {
146+
uint256 absoluteFee = (feePerc.abs() * amount) / HUNDRED_PERC;
147+
int256 feeAmt = feePerc.sign() * absoluteFee.toInt256();
148+
149+
// when fee is to be paid by the user or when debasement is allowed
150+
// use the entire fee amount
151+
if (feeAmt >= 0 || allowDebase) {
152+
return feeAmt;
153+
}
154+
155+
// fee is to be paid to the user and debasement is not allowed
156+
// pay out only till the reserve is depleted
157+
return -1 * MathUpgradeable.min(feeToken.balanceOf(feeReserve), absoluteFee).toInt256();
122158
}
123159
}

spot-contracts/contracts/strategies/CDRPricingStrategy.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
import { TrancheHelpers } from "../_utils/BondHelpers.sol";
55

spot-contracts/contracts/strategies/TrancheClassDiscountStrategy.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.19;
33

44
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
55
import { TrancheData, TrancheDataHelpers, BondHelpers } from "../_utils/BondHelpers.sol";

0 commit comments

Comments
 (0)