Skip to content
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
3 changes: 2 additions & 1 deletion spot-contracts/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {
mocha: true,
node: true,
},
plugins: ["@typescript-eslint"],
plugins: ["@typescript-eslint", "no-only-tests"],
extends: ["standard", "plugin:prettier/recommended", "plugin:node/recommended"],
parser: "@typescript-eslint/parser",
parserOptions: {
Expand All @@ -26,5 +26,6 @@ module.exports = {
allowModules: ["hardhat", "ethers", "@openzeppelin/upgrades-core"],
},
],
"no-only-tests/no-only-tests": "error",
},
};
2 changes: 1 addition & 1 deletion spot-contracts/contracts/BondIssuer.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { EnumerableSetUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
Expand Down
2 changes: 1 addition & 1 deletion spot-contracts/contracts/PerpetualTranche.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
Expand Down
2 changes: 1 addition & 1 deletion spot-contracts/contracts/RouterV1.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
Expand Down
96 changes: 96 additions & 0 deletions spot-contracts/contracts/_interfaces/IVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

/// @notice Expected asset to be a valid vault asset.
/// @param token Address of the token.
error UnexpectedAsset(IERC20Upgradeable token);

/// @notice Expected transfer out asset to not be a vault asset.
/// @param token Address of the token transferred.
error UnauthorizedTransferOut(IERC20Upgradeable token);

/// @notice Expected vault assets to be deployed.
error NoDeployment();

/*
* @title IVault
*
* @notice The standard interface for a generic vault as described by the "Vault Framework".
* http://thinking.farm/essays/2022-10-05-mechanical-finance/
*
* Users deposit a "underlying" asset and mint "notes" (or vault shares).
* The vault "deploys" underlying asset in a rules-based fashion (through a hard-coded strategy)
* to "earn" income. It "recovers" deployed assets once the investment matures.
*
* The vault operates through two external poke functions which off-chain keepers can execute.
* 1) `deploy`: When executed, the vault "puts to work" the underlying assets it holds. The vault
* usually returns other ERC-20 tokens which act as receipts of the deployment.
* 2) `recover`: When executed, the vault turns in the receipts and retrieves the underlying asset and
* usually collects some yield for this work.
*
* The rules of the deployment and recovery are specific to the vault strategy.
*
* At any time the vault will hold multiple ERC20 tokens, together referred to as the vault's "assets".
* They can be a combination of the underlying asset, the earned asset and the deployed assets (receipts).
*
* On redemption users burn their "notes" to receive a proportional slice of all the vault's assets.
*
*/

interface IVault {
/// @notice Recovers deployed funds and redeploys them.
function recoverAndRedeploy() external;

/// @notice Deploys deposited funds.
function deploy() external;

/// @notice Recovers deployed funds.
function recover() external;

/// @notice Deposits the underlying asset from {msg.sender} into the vault and mints notes.
/// @param amount The amount tokens to be deposited into the vault.
/// @return The amount of notes.
function deposit(uint256 amount) external returns (uint256);

struct TokenAmount {
/// @notice The asset token redeemed.
IERC20Upgradeable token;
/// @notice The amount redeemed.
uint256 amount;
}

/// @notice Burns notes and sends a proportional share of vault's assets back to {msg.sender}.
/// @param notes The amount of notes to be burnt.
/// @return The list of asset tokens and amounts redeemed.
function redeem(uint256 notes) external returns (TokenAmount[] memory);

/// @return The total value of assets currently held by the vault, denominated in a standard unit of account.
function getTVL() external returns (uint256);

/// @notice The ERC20 token that can be deposited into this vault.
function underlying() external view returns (IERC20Upgradeable);

/// @param token The address of the asset ERC-20 token held by the vault.
/// @return The vault's asset token balance.
function vaultAssetBalance(IERC20Upgradeable token) external view returns (uint256);

/// @return Total count of deployed asset tokens held by the vault.
function deployedCount() external view returns (uint256);

/// @param i The index of a token.
/// @return The token address from the deployed asset token list by index.
function deployedAt(uint256 i) external view returns (IERC20Upgradeable);

/// @return Total count of earned income tokens held by the vault.
function earnedCount() external pure returns (uint256);

/// @param i The index of a token.
/// @return The token address from the earned income token list by index.
function earnedAt(uint256 i) external view returns (IERC20Upgradeable);

/// @param token The address of a token to check.
/// @return If the given token is held by the vault.
function isVaultAsset(IERC20Upgradeable token) external view returns (bool);
}
2 changes: 1 addition & 1 deletion spot-contracts/contracts/_utils/BondHelpers.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
Expand Down
2 changes: 1 addition & 1 deletion spot-contracts/contracts/_utils/SignedMathHelpers.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

/**
* @title SignedMathHelpers
Expand Down
54 changes: 45 additions & 9 deletions spot-contracts/contracts/strategies/BasicFeeStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { SafeCastUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import { SignedMathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/SignedMathUpgradeable.sol";
import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import { SignedMathHelpers } from "../_utils/SignedMathHelpers.sol";

import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
Expand All @@ -26,8 +27,8 @@ error UnacceptablePercValue(int256 perc);
contract BasicFeeStrategy is IFeeStrategy, OwnableUpgradeable {
using SignedMathUpgradeable for int256;
using SignedMathHelpers for int256;
using SafeCastUpgradeable for uint256;
using SafeCastUpgradeable for int256;
using SafeCastUpgradeable for uint256;

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

/// @notice The address of the fee reserve.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address public immutable feeReserve;

/// @notice Fixed percentage of the mint amount to be used as fee.
int256 public mintFeePerc;

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

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

// EVENTS

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

/// @notice Event emitted when the debasement rule is updated.
/// @param allow If debasement is allowed or not.
event UpdatedDebasementRule(bool allow);

/// @notice Contract constructor.
/// @param feeToken_ Address of the fee ERC-20 token contract.
constructor(IERC20Upgradeable feeToken_) {
/// @param feeReserve_ Address of the fee reserve.
constructor(IERC20Upgradeable feeToken_, address feeReserve_) {
feeToken = feeToken_;
feeReserve = feeReserve_;
}

/// @notice Contract initializer.
Expand Down Expand Up @@ -103,21 +119,41 @@ contract BasicFeeStrategy is IFeeStrategy, OwnableUpgradeable {
emit UpdatedRolloverPerc(rolloverFeePerc_);
}

/// @notice Update debasement rule.
/// @param allow If debasement is allowed or not.
function allowDebasement(bool allow) public onlyOwner {
allowDebase = allow;
emit UpdatedDebasementRule(allow);
}

/// @inheritdoc IFeeStrategy
function computeMintFees(uint256 mintAmt) external view override returns (int256, uint256) {
uint256 absoluteFee = (mintFeePerc.abs() * mintAmt) / HUNDRED_PERC;
return (mintFeePerc.sign() * absoluteFee.toInt256(), 0);
return (_computeFeeAmt(mintAmt, mintFeePerc), 0);
}

/// @inheritdoc IFeeStrategy
function computeBurnFees(uint256 burnAmt) external view override returns (int256, uint256) {
uint256 absoluteFee = (burnFeePerc.abs() * burnAmt) / HUNDRED_PERC;
return (burnFeePerc.sign() * absoluteFee.toInt256(), 0);
return (_computeFeeAmt(burnAmt, burnFeePerc), 0);
}

/// @inheritdoc IFeeStrategy
function computeRolloverFees(uint256 rolloverAmt) external view override returns (int256, uint256) {
uint256 absoluteFee = (rolloverFeePerc.abs() * rolloverAmt) / HUNDRED_PERC;
return (rolloverFeePerc.sign() * absoluteFee.toInt256(), 0);
return (_computeFeeAmt(rolloverAmt, rolloverFeePerc), 0);
}

/// @dev Given the token amount and fee percentage, computes the integer amount to be charged as fees.
function _computeFeeAmt(uint256 amount, int256 feePerc) private view returns (int256) {
uint256 absoluteFee = (feePerc.abs() * amount) / HUNDRED_PERC;
int256 feeAmt = feePerc.sign() * absoluteFee.toInt256();

// when fee is to be paid by the user or when debasement is allowed
// use the entire fee amount
if (feeAmt >= 0 || allowDebase) {
return feeAmt;
}

// fee is to be paid to the user and debasement is not allowed
// pay out only till the reserve is depleted
return -1 * MathUpgradeable.min(feeToken.balanceOf(feeReserve), absoluteFee).toInt256();
}
}
2 changes: 1 addition & 1 deletion spot-contracts/contracts/strategies/CDRPricingStrategy.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { TrancheData, TrancheDataHelpers, BondHelpers } from "../_utils/BondHelpers.sol";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { ITranche } from "../_interfaces/buttonwood/ITranche.sol";
Expand Down
2 changes: 1 addition & 1 deletion spot-contracts/contracts/test/BondHelpersTester.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { BondHelpers, TrancheData, TrancheDataHelpers } from "../_utils/BondHelpers.sol";
import { IBondController } from "../_interfaces/buttonwood/IBondController.sol";
Expand Down
2 changes: 1 addition & 1 deletion spot-contracts/contracts/test/MathTester.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { SignedMathHelpers } from "../_utils/SignedMathHelpers.sol";

Expand Down
19 changes: 0 additions & 19 deletions spot-contracts/contracts/test/mocks/MockBondIssuer.sol

This file was deleted.

28 changes: 0 additions & 28 deletions spot-contracts/contracts/test/mocks/MockDiscountStrategy.sol

This file was deleted.

2 changes: 1 addition & 1 deletion spot-contracts/contracts/test/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;
pragma solidity ^0.8.19;

import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";

Expand Down
Loading