Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

AP-786 Strategy Cleanup #397

Merged
merged 4 commits into from
Oct 3, 2023
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
32 changes: 23 additions & 9 deletions contracts/core/strategy/APStrategy_V1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,43 @@ import {IStrategy} from "./IStrategy.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";

abstract contract APStrategy_V1 is IStrategy, Pausable {
/*//////////////////////////////////////////////////////////////
CONFIG
//////////////////////////////////////////////////////////////*/
/*** CONSTNATS ***/
uint256 constant EXP_SCALE = 1e18;

/*** STORAGE ***/
StrategyConfig public config;

constructor(StrategyConfig memory _config) {
config = _config;
}
/*//////////////////////////////////////////////////////////////
CONFIG
//////////////////////////////////////////////////////////////*/

/// @notice Returns the config struct
/// @return Config the current strategy config
function getStrategyConfig() external view returns (StrategyConfig memory) {
return config;
}

/// @notice Set the strategy config
/// @dev This method must be access controlled. The config overwrites the stored config in its entirety
/// @param _config The StrategyConfig that will be stored and referenced within the strategy contract
function setStrategyConfig(StrategyConfig memory _config) external onlyAdmin {
config = _config;
emit ConfigChanged(config);
}

/// @notice Check whether the contract is paused
/// @dev Make public the state of the Pausable contract's `paused` state
/// @return paused the current state of the paused boolean
function paused() public view override(IStrategy, Pausable) returns (bool) {
return super.paused();
}

/*//////////////////////////////////////////////////////////////
ADMIN
//////////////////////////////////////////////////////////////*/

modifier onlyAdmin() {
require(_msgSender() == config.admin);
if (_msgSender() != config.admin) revert AdminOnly();
_;
}

Expand All @@ -41,8 +54,9 @@ abstract contract APStrategy_V1 is IStrategy, Pausable {
_unpause();
}

function paused() public view virtual override(IStrategy, Pausable) returns (bool) {
return super.paused();
modifier nonZeroAmount(uint256 amt) {
if (amt == 0) revert ZeroAmount();
_;
}

/*//////////////////////////////////////////////////////////////
Expand Down
92 changes: 18 additions & 74 deletions contracts/integrations/flux/FluxStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,21 @@
// author: @stevieraykatz
pragma solidity ^0.8.19;

import {IStrategy} from "../../core/strategy/IStrategy.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {APStrategy_V1} from "../../core/strategy/APStrategy_V1.sol";
import {IFlux} from "./IFlux.sol";
import {FixedPointMathLib} from "../../lib/FixedPointMathLib.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract FluxStrategy is IStrategy, Pausable, ReentrancyGuard {
contract FluxStrategy is APStrategy_V1, ReentrancyGuard {
using FixedPointMathLib for uint256;

/*** CONSTNATS ***/
uint256 constant expScale = 1e18;

/*** STORAGE ***/
StrategyConfig public config;
using SafeERC20 for IERC20;

constructor(StrategyConfig memory _config) {
config = _config;
}

/*//////////////////////////////////////////////////////////////
ADMIN
//////////////////////////////////////////////////////////////*/

modifier onlyAdmin() {
if (_msgSender() != config.admin) revert AdminOnly();
_;
}

modifier nonZeroAmount(uint256 amt) {
if (amt == 0) revert ZeroAmount();
_;
}

function pause() external onlyAdmin {
_pause();
}

function unpause() external onlyAdmin {
_unpause();
}

/// @notice Check whether the contract is paused
/// @dev Make public the state of the Pausable contract's `paused` state
/// @return paused the current state of the paused boolean
function paused() public view override(IStrategy, Pausable) returns (bool) {
return super.paused();
}

/*//////////////////////////////////////////////////////////////
CONFIG
//////////////////////////////////////////////////////////////*/
/// @notice Returns the config struct
/// @return Config the current strategy config
function getStrategyConfig() external view returns (StrategyConfig memory) {
return config;
}

/// @notice Set the strategy config
/// @dev This method must be access controlled. The config overwrites the stored config in its entirety
/// @param _newConfig The StrategyConfig that willbe stored and referenced within the strategy contract
function setStrategyConfig(StrategyConfig memory _newConfig) external onlyAdmin {
config = _newConfig;
emit ConfigChanged(config);
}

/*//////////////////////////////////////////////////////////////
IMPLEMENTATION
//////////////////////////////////////////////////////////////*/
Expand All @@ -79,13 +29,9 @@ contract FluxStrategy is IStrategy, Pausable, ReentrancyGuard {
/// @return yieldTokenAmt the qty of `config.yieldToken` that were yielded from the deposit action
function deposit(
uint256 amt
) external payable whenNotPaused nonReentrant nonZeroAmount(amt) returns (uint256) {
if (!IFlux(config.baseToken).transferFrom(_msgSender(), address(this), amt)) {
revert TransferFailed();
}
if (!IFlux(config.baseToken).approve(config.yieldToken, amt)) {
revert ApproveFailed();
}
) external payable override whenNotPaused nonReentrant nonZeroAmount(amt) returns (uint256) {
IERC20(config.baseToken).safeTransferFrom(_msgSender(), address(this), amt);
IERC20(config.baseToken).safeApprove(config.yieldToken, amt);
uint256 yieldTokens = _enterPosition(amt);
if (!IFlux(config.yieldToken).approve(_msgSender(), yieldTokens)) {
revert ApproveFailed();
Expand All @@ -103,17 +49,15 @@ contract FluxStrategy is IStrategy, Pausable, ReentrancyGuard {
/// @return baseTokenAmt the qty of `config.baseToken` that are approved for transfer by msg.sender
function withdraw(
uint256 amt
) external payable whenNotPaused nonReentrant nonZeroAmount(amt) returns (uint256) {
) external payable override whenNotPaused nonReentrant nonZeroAmount(amt) returns (uint256) {
if (!IFlux(config.yieldToken).transferFrom(_msgSender(), address(this), amt)) {
revert TransferFailed();
}
if (!IFlux(config.yieldToken).approve(config.yieldToken, amt)) {
revert ApproveFailed();
}
uint256 baseTokens = _withdrawPosition(amt);
if (!IFlux(config.baseToken).approve(_msgSender(), baseTokens)) {
revert ApproveFailed();
}
IERC20(config.baseToken).safeApprove(_msgSender(), baseTokens);
emit WithdrewPosition(amt, baseTokens);
return baseTokens;
}
Expand All @@ -122,22 +66,22 @@ contract FluxStrategy is IStrategy, Pausable, ReentrancyGuard {
/// @dev This method expects that the `amt` provided is denominated in `baseToken`
/// @param amt the qty of the `baseToken` that should be checked for conversion rates
/// @return yieldTokenAmt the expected qty of `yieldToken` if this strategy received `amt` of `baseToken`
function previewDeposit(uint256 amt) external view returns (uint256) {
// Exchange Rate == (expScale * USDC) / fUSDC
function previewDeposit(uint256 amt) external view override returns (uint256) {
// Exchange Rate == (EXP_SCALE * USDC) / fUSDC
uint256 exRate = IFlux(config.yieldToken).exchangeRateStored();
// Expected fUSDC == (amtUSDC * expScale / exRate)
return amt.mulDivDown(expScale, exRate);
// Expected fUSDC == (amtUSDC * EXP_SCALE / exRate)
return amt.mulDivDown(EXP_SCALE, exRate);
}

/// @notice Provide an estimate for the current exchange rate for a given withdrawal
/// @dev This method expects that the `amt` provided is denominated in `yieldToken`
/// @param amt the qty of the `yieldToken` that should be checked for conversion rates
/// @return yieldTokenAmt the expected qty of `baseToken` if this strategy received `amt` of `yieldToken`
function previewWithdraw(uint256 amt) external view returns (uint256) {
// Exchange Rate == (expScale * USDC) / fUSDC
function previewWithdraw(uint256 amt) external view override returns (uint256) {
// Exchange Rate == (EXP_SCALE * USDC) / fUSDC
uint256 exRate = IFlux(config.yieldToken).exchangeRateStored();
// Expected USDC == (amtfUSDC * exRate) / expScale
return amt.mulDivDown(exRate, expScale);
// Expected USDC == (amtfUSDC * exRate) / EXP_SCALE
return amt.mulDivDown(exRate, EXP_SCALE);
}

/*//////////////////////////////////////////////////////////////
Expand Down
12 changes: 9 additions & 3 deletions test/integrations/flux/FluxStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,19 @@ describe("FluxStrategy", function () {
it("reverts if the baseToken transfer fails", async function () {
await wait(baseToken.mint(await owner.getAddress(), 1));
await wait(baseToken.setTransferAllowed(false));
await expect(flux.deposit(1)).to.be.revertedWithCustomError(flux, "TransferFailed");
await expect(flux.deposit(1)).to.be.revertedWith(
"SafeERC20: ERC20 operation did not succeed"
);
await wait(baseToken.setTransferAllowed(true));
});
it("reverts if the baseToken approve fails", async function () {
await wait(baseToken.mint(await owner.getAddress(), 1));
await wait(baseToken.approve(flux.address, 1));
await wait(baseToken.setApproveAllowed(false));
await wait(yieldToken.setResponseAmt(1));
await expect(flux.deposit(1)).to.be.revertedWithCustomError(flux, "ApproveFailed");
await expect(flux.deposit(1)).to.be.revertedWith(
"SafeERC20: ERC20 operation did not succeed"
);
await wait(baseToken.setApproveAllowed(true));
});
it("reverts if the deposit fails", async function () {
Expand Down Expand Up @@ -216,7 +220,9 @@ describe("FluxStrategy", function () {
await wait(yieldToken.approve(flux.address, 1));
await wait(yieldToken.setResponseAmt(1));
await wait(baseToken.setApproveAllowed(false));
await expect(flux.withdraw(1)).to.be.revertedWithCustomError(flux, "ApproveFailed");
await expect(flux.withdraw(1)).to.be.revertedWith(
"SafeERC20: ERC20 operation did not succeed"
);
await wait(baseToken.setApproveAllowed(true));
});
it("correctly executes the redemption", async function () {
Expand Down