@shift-defi/strategies 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,223 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ /**
5
+ * @title IPoolAddressesProvider
6
+ * @author Aave
7
+ * @notice Defines the basic interface for a Pool Addresses Provider.
8
+ */
9
+ interface IPoolAddressesProvider {
10
+ /**
11
+ * @dev Emitted when the market identifier is updated.
12
+ * @param oldMarketId The old id of the market
13
+ * @param newMarketId The new id of the market
14
+ */
15
+ event MarketIdSet(string indexed oldMarketId, string indexed newMarketId);
16
+
17
+ /**
18
+ * @dev Emitted when the pool is updated.
19
+ * @param oldAddress The old address of the Pool
20
+ * @param newAddress The new address of the Pool
21
+ */
22
+ event PoolUpdated(address indexed oldAddress, address indexed newAddress);
23
+
24
+ /**
25
+ * @dev Emitted when the pool configurator is updated.
26
+ * @param oldAddress The old address of the PoolConfigurator
27
+ * @param newAddress The new address of the PoolConfigurator
28
+ */
29
+ event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress);
30
+
31
+ /**
32
+ * @dev Emitted when the price oracle is updated.
33
+ * @param oldAddress The old address of the PriceOracle
34
+ * @param newAddress The new address of the PriceOracle
35
+ */
36
+ event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress);
37
+
38
+ /**
39
+ * @dev Emitted when the ACL manager is updated.
40
+ * @param oldAddress The old address of the ACLManager
41
+ * @param newAddress The new address of the ACLManager
42
+ */
43
+ event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress);
44
+
45
+ /**
46
+ * @dev Emitted when the ACL admin is updated.
47
+ * @param oldAddress The old address of the ACLAdmin
48
+ * @param newAddress The new address of the ACLAdmin
49
+ */
50
+ event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress);
51
+
52
+ /**
53
+ * @dev Emitted when the price oracle sentinel is updated.
54
+ * @param oldAddress The old address of the PriceOracleSentinel
55
+ * @param newAddress The new address of the PriceOracleSentinel
56
+ */
57
+ event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress);
58
+
59
+ /**
60
+ * @dev Emitted when the pool data provider is updated.
61
+ * @param oldAddress The old address of the PoolDataProvider
62
+ * @param newAddress The new address of the PoolDataProvider
63
+ */
64
+ event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress);
65
+
66
+ /**
67
+ * @dev Emitted when a new proxy is created.
68
+ * @param id The identifier of the proxy
69
+ * @param proxyAddress The address of the created proxy contract
70
+ * @param implementationAddress The address of the implementation contract
71
+ */
72
+ event ProxyCreated(bytes32 indexed id, address indexed proxyAddress, address indexed implementationAddress);
73
+
74
+ /**
75
+ * @dev Emitted when a new non-proxied contract address is registered.
76
+ * @param id The identifier of the contract
77
+ * @param oldAddress The address of the old contract
78
+ * @param newAddress The address of the new contract
79
+ */
80
+ event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress);
81
+
82
+ /**
83
+ * @dev Emitted when the implementation of the proxy registered with id is updated
84
+ * @param id The identifier of the contract
85
+ * @param proxyAddress The address of the proxy contract
86
+ * @param oldImplementationAddress The address of the old implementation contract
87
+ * @param newImplementationAddress The address of the new implementation contract
88
+ */
89
+ event AddressSetAsProxy(
90
+ bytes32 indexed id,
91
+ address indexed proxyAddress,
92
+ address oldImplementationAddress,
93
+ address indexed newImplementationAddress
94
+ );
95
+
96
+ /**
97
+ * @notice Returns the id of the Aave market to which this contract points to.
98
+ * @return The market id
99
+ */
100
+ function getMarketId() external view returns (string memory);
101
+
102
+ /**
103
+ * @notice Associates an id with a specific PoolAddressesProvider.
104
+ * @dev This can be used to create an onchain registry of PoolAddressesProviders to
105
+ * identify and validate multiple Aave markets.
106
+ * @param newMarketId The market id
107
+ */
108
+ function setMarketId(string calldata newMarketId) external;
109
+
110
+ /**
111
+ * @notice Returns an address by its identifier.
112
+ * @dev The returned address might be an EOA or a contract, potentially proxied
113
+ * @dev It returns ZERO if there is no registered address with the given id
114
+ * @param id The id
115
+ * @return The address of the registered for the specified id
116
+ */
117
+ function getAddress(bytes32 id) external view returns (address);
118
+
119
+ /**
120
+ * @notice General function to update the implementation of a proxy registered with
121
+ * certain `id`. If there is no proxy registered, it will instantiate one and
122
+ * set as implementation the `newImplementationAddress`.
123
+ * @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit
124
+ * setter function, in order to avoid unexpected consequences
125
+ * @param id The id
126
+ * @param newImplementationAddress The address of the new implementation
127
+ */
128
+ function setAddressAsProxy(bytes32 id, address newImplementationAddress) external;
129
+
130
+ /**
131
+ * @notice Sets an address for an id replacing the address saved in the addresses map.
132
+ * @dev IMPORTANT Use this function carefully, as it will do a hard replacement
133
+ * @param id The id
134
+ * @param newAddress The address to set
135
+ */
136
+ function setAddress(bytes32 id, address newAddress) external;
137
+
138
+ /**
139
+ * @notice Returns the address of the Pool proxy.
140
+ * @return The Pool proxy address
141
+ */
142
+ function getPool() external view returns (address);
143
+
144
+ /**
145
+ * @notice Updates the implementation of the Pool, or creates a proxy
146
+ * setting the new `pool` implementation when the function is called for the first time.
147
+ * @param newPoolImpl The new Pool implementation
148
+ */
149
+ function setPoolImpl(address newPoolImpl) external;
150
+
151
+ /**
152
+ * @notice Returns the address of the PoolConfigurator proxy.
153
+ * @return The PoolConfigurator proxy address
154
+ */
155
+ function getPoolConfigurator() external view returns (address);
156
+
157
+ /**
158
+ * @notice Updates the implementation of the PoolConfigurator, or creates a proxy
159
+ * setting the new `PoolConfigurator` implementation when the function is called for the first time.
160
+ * @param newPoolConfiguratorImpl The new PoolConfigurator implementation
161
+ */
162
+ function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external;
163
+
164
+ /**
165
+ * @notice Returns the address of the price oracle.
166
+ * @return The address of the PriceOracle
167
+ */
168
+ function getPriceOracle() external view returns (address);
169
+
170
+ /**
171
+ * @notice Updates the address of the price oracle.
172
+ * @param newPriceOracle The address of the new PriceOracle
173
+ */
174
+ function setPriceOracle(address newPriceOracle) external;
175
+
176
+ /**
177
+ * @notice Returns the address of the ACL manager.
178
+ * @return The address of the ACLManager
179
+ */
180
+ function getACLManager() external view returns (address);
181
+
182
+ /**
183
+ * @notice Updates the address of the ACL manager.
184
+ * @param newAclManager The address of the new ACLManager
185
+ */
186
+ function setACLManager(address newAclManager) external;
187
+
188
+ /**
189
+ * @notice Returns the address of the ACL admin.
190
+ * @return The address of the ACL admin
191
+ */
192
+ function getACLAdmin() external view returns (address);
193
+
194
+ /**
195
+ * @notice Updates the address of the ACL admin.
196
+ * @param newAclAdmin The address of the new ACL admin
197
+ */
198
+ function setACLAdmin(address newAclAdmin) external;
199
+
200
+ /**
201
+ * @notice Returns the address of the price oracle sentinel.
202
+ * @return The address of the PriceOracleSentinel
203
+ */
204
+ function getPriceOracleSentinel() external view returns (address);
205
+
206
+ /**
207
+ * @notice Updates the address of the price oracle sentinel.
208
+ * @param newPriceOracleSentinel The address of the new PriceOracleSentinel
209
+ */
210
+ function setPriceOracleSentinel(address newPriceOracleSentinel) external;
211
+
212
+ /**
213
+ * @notice Returns the address of the data provider.
214
+ * @return The address of the DataProvider
215
+ */
216
+ function getPoolDataProvider() external view returns (address);
217
+
218
+ /**
219
+ * @notice Updates the address of the data provider.
220
+ * @param newDataProvider The address of the new DataProvider
221
+ */
222
+ function setPoolDataProvider(address newDataProvider) external;
223
+ }
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ /**
5
+ * @title IScaledBalanceToken
6
+ * @author Aave
7
+ * @notice Defines the basic interface for a scaled-balance token.
8
+ */
9
+ interface IScaledBalanceToken {
10
+ /**
11
+ * @dev Emitted after the mint action
12
+ * @param caller The address performing the mint
13
+ * @param onBehalfOf The address of the user that will receive the minted tokens
14
+ * @param value The scaled-up amount being minted (based on user entered amount and balance increase from interest)
15
+ * @param balanceIncrease The increase in scaled-up balance since the last action of 'onBehalfOf'
16
+ * @param index The next liquidity index of the reserve
17
+ */
18
+ event Mint(
19
+ address indexed caller,
20
+ address indexed onBehalfOf,
21
+ uint256 value,
22
+ uint256 balanceIncrease,
23
+ uint256 index
24
+ );
25
+
26
+ /**
27
+ * @dev Emitted after the burn action
28
+ * @dev If the burn function does not involve a transfer of the underlying asset, the target defaults to zero address
29
+ * @param from The address from which the tokens will be burned
30
+ * @param target The address that will receive the underlying, if any
31
+ * @param value The scaled-up amount being burned (user entered amount - balance increase from interest)
32
+ * @param balanceIncrease The increase in scaled-up balance since the last action of 'from'
33
+ * @param index The next liquidity index of the reserve
34
+ */
35
+ event Burn(address indexed from, address indexed target, uint256 value, uint256 balanceIncrease, uint256 index);
36
+
37
+ /**
38
+ * @notice Returns the scaled balance of the user.
39
+ * @dev The scaled balance is the sum of all the updated stored balance divided by the reserve's liquidity index
40
+ * at the moment of the update
41
+ * @param user The user whose balance is calculated
42
+ * @return The scaled balance of the user
43
+ */
44
+ function scaledBalanceOf(address user) external view returns (uint256);
45
+
46
+ /**
47
+ * @notice Returns the scaled balance of the user and the scaled total supply.
48
+ * @param user The address of the user
49
+ * @return The scaled balance of the user
50
+ * @return The scaled total supply
51
+ */
52
+ function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);
53
+
54
+ /**
55
+ * @notice Returns the scaled total supply of the scaled balance token. Represents sum(debt/index)
56
+ * @return The scaled total supply
57
+ */
58
+ function scaledTotalSupply() external view returns (uint256);
59
+
60
+ /**
61
+ * @notice Returns last index interest was accrued to the user's balance
62
+ * @param user The address of the user
63
+ * @return The last index interest was accrued to the user's balance, expressed in ray
64
+ */
65
+ function getPreviousIndex(address user) external view returns (uint256);
66
+ }
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ interface IAngleMerkleDistributor {
5
+ function claim(
6
+ address[] calldata users,
7
+ address[] calldata tokens,
8
+ uint256[] calldata amounts,
9
+ bytes32[][] calldata proofs
10
+ ) external;
11
+ }
@@ -0,0 +1,17 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+ import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
4
+
5
+ interface IcASSETv3 is IERC20 {
6
+ function baseToken() external view returns (address);
7
+
8
+ function supply(address, uint) external;
9
+
10
+ function withdraw(address, uint) external;
11
+
12
+ function accrueAccount(address) external;
13
+ }
14
+
15
+ interface IcRewards {
16
+ function claim(address, address, bool) external;
17
+ }
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ interface ICurveStableSwapNG {
5
+ function coins(uint256 index) external view returns (address);
6
+
7
+ function get_balances() external view returns (uint256[] memory);
8
+
9
+ function totalSupply() external view returns (uint256);
10
+
11
+ function add_liquidity(uint256[] memory, uint256) external;
12
+
13
+ function remove_liquidity(uint256, uint256[] memory) external;
14
+
15
+ function get_virtual_price() external view returns (uint256);
16
+ }
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ interface ILiquidityGaugeV6 {
5
+ function lp_token() external view returns (address);
6
+
7
+ function balanceOf(address) external view returns (uint256);
8
+
9
+ function deposit(uint256) external;
10
+
11
+ function withdraw(uint256) external;
12
+
13
+ function claim_rewards() external;
14
+
15
+ function reward_count() external view returns (uint256);
16
+ }
@@ -0,0 +1,6 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ interface IFluidLendingResolver {
5
+ function computeFToken(address asset, string memory fTokenType) external view returns (address);
6
+ }
@@ -0,0 +1,26 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ interface IFluidMerkleDistributor {
5
+ /// @notice Claims rewards for a given recipient
6
+ /// @param recipient_ - address of the recipient
7
+ /// @param cumulativeAmount_ - cumulative amount of rewards to claim
8
+ /// @param positionType_ - type of position, 1 for lending, 2 for vaults, 3 for smart lending, etc
9
+ /// @param positionId_ - id of the position, fToken address for lending and vaultId for vaults
10
+ /// @param cycle_ - cycle of the rewards
11
+ /// @param merkleProof_ - merkle proof of the rewards
12
+ /// @param metadata_ - metadata of the rewards
13
+ function claim(
14
+ address recipient_,
15
+ uint256 cumulativeAmount_,
16
+ uint8 positionType_,
17
+ bytes32 positionId_,
18
+ uint256 cycle_,
19
+ bytes32[] calldata merkleProof_,
20
+ bytes calldata metadata_
21
+ ) external;
22
+
23
+ /// @notice Gets the address of the reward token
24
+ /// @return The address of the reward token
25
+ function TOKEN() external view returns (address);
26
+ }
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ interface IFluidToken {
5
+ function asset() external view returns (address);
6
+
7
+ function deposit(uint256 assets_, address receiver_) external returns (uint256 shares);
8
+
9
+ function redeem(uint256 shares_, address receiver_, address owner_) external returns (uint256 assets_);
10
+
11
+ function convertToAssets(uint256 shares_) external view returns (uint256);
12
+
13
+ function convertToShares(uint256 assets_) external view returns (uint256);
14
+ }
@@ -0,0 +1,255 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
5
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
+ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
7
+ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
8
+
9
+ import {StrategyTemplate} from "@shift-defi/core/StrategyTemplate.sol";
10
+ import {IStrategyTemplate} from "@shift-defi/core/interfaces/IStrategyTemplate.sol";
11
+ import {Errors} from "@shift-defi/core/libraries/Errors.sol";
12
+
13
+ import {IFluidSupply} from "../interfaces/IFluidSupply.sol";
14
+ import {IFluidMerkleDistributor} from "../dependencies/fluid/IFluidMerkleDistributor.sol";
15
+ import {IFluidToken} from "../dependencies/fluid/IFluidToken.sol";
16
+
17
+ contract FluidSupply is AccessControlUpgradeable, IFluidSupply, StrategyTemplate {
18
+ using Math for uint256;
19
+ using SafeERC20 for IERC20;
20
+
21
+ /// @notice State ID for the Fluid supply state (asset supplied to Fluid)
22
+ bytes32 private constant FLUID_SUPPLY_STATE_ID = keccak256("FLUID_SUPPLY_STATE_ID");
23
+
24
+ /// @notice State ID for the token only state (holding the asset directly)
25
+ bytes32 private constant UNDERLYING_ASSET_STATE_ID = keccak256("UNDERLYING_ASSET_STATE_ID");
26
+
27
+ /// @notice Role for the Merkle claimer
28
+ bytes32 private constant MERKLE_CLAIMER_ROLE = keccak256("MERKLE_CLAIMER_ROLE");
29
+
30
+ /// @notice Position type for lending positions of Fluid merkle rewards
31
+ uint8 private constant POSITION_TYPE_LENDING = 1;
32
+
33
+ /// @notice The Fluid fToken address
34
+ address public fToken;
35
+
36
+ /// @notice The underlying asset address
37
+ address public underlyingAsset;
38
+
39
+ /// @notice The last recorded value of vault token amount * share price
40
+ uint256 public lastAssetsValue;
41
+
42
+ /// @notice The address of the Fluid Merkle distributor contract
43
+ address public merkleDistributor;
44
+
45
+ /// @notice The address of the Merkle reward token
46
+ address public merkleRewardToken;
47
+
48
+ struct SlippageParams {
49
+ uint256 enterMaxSlippage;
50
+ uint256 exitMaxSlippage;
51
+ uint256 emergencyExitMaxSlippage;
52
+ }
53
+
54
+ constructor() {
55
+ _disableInitializers();
56
+ }
57
+
58
+ /// @notice Initializes the FluidSupply strategy contract
59
+ /// @dev Sets up the Fluid fToken and asset addresses, and configures the strategy states
60
+ /// @param strategyContainer The address of the strategy container contract
61
+ /// @param defaultAdmin The address of the default admin
62
+ /// @param merkleClaimer The address of the Merkle claimer
63
+ /// @param _fToken The address of the strategy's fToken
64
+ /// @param _merkleDistributor The address of the Merkle distributor
65
+ function initialize(
66
+ address strategyContainer,
67
+ address defaultAdmin,
68
+ address merkleClaimer,
69
+ address _fToken,
70
+ address _merkleDistributor,
71
+ SlippageParams memory slippageParams
72
+ ) external initializer {
73
+ __AccessControl_init();
74
+
75
+ __StrategyTemplate_init(
76
+ strategyContainer,
77
+ slippageParams.enterMaxSlippage,
78
+ slippageParams.exitMaxSlippage,
79
+ slippageParams.emergencyExitMaxSlippage
80
+ );
81
+
82
+ require(defaultAdmin != address(0), Errors.ZeroAddress());
83
+ _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
84
+ require(merkleClaimer != address(0), Errors.ZeroAddress());
85
+ _grantRole(MERKLE_CLAIMER_ROLE, merkleClaimer);
86
+
87
+ require(_fToken != address(0), Errors.ZeroAddress());
88
+ fToken = _fToken;
89
+ underlyingAsset = IFluidToken(_fToken).asset();
90
+
91
+ require(_merkleDistributor != address(0), Errors.ZeroAddress());
92
+ merkleDistributor = _merkleDistributor;
93
+
94
+ merkleRewardToken = IFluidMerkleDistributor(_merkleDistributor).TOKEN();
95
+
96
+ _setState(UNDERLYING_ASSET_STATE_ID, false, false, true, 0);
97
+ _setState(FLUID_SUPPLY_STATE_ID, true, true, false, 1);
98
+ }
99
+
100
+ /// @inheritdoc IStrategyTemplate
101
+ function stateNav(bytes32 stateId) public view override returns (uint256) {
102
+ if (stateId == FLUID_SUPPLY_STATE_ID) {
103
+ return fluidNav();
104
+ } else if (stateId == UNDERLYING_ASSET_STATE_ID) {
105
+ return underlyingAssetNav();
106
+ } else if (stateId == NO_ALLOCATION_STATE_ID) {
107
+ return 0;
108
+ } else {
109
+ revert StateNotFound(stateId);
110
+ }
111
+ }
112
+
113
+ /// @notice Calculates the NAV for the Fluid supply state
114
+ /// @dev Returns the value of fTokens held by the contract in notion
115
+ /// @return The NAV of fluid supply state in notion
116
+ function fluidNav() public view returns (uint256) {
117
+ address fTokenCached = fToken;
118
+ uint256 fTokenBalance = IERC20(fTokenCached).balanceOf(address(this));
119
+ return getTokenAmountInNotion(underlyingAsset, IFluidToken(fTokenCached).convertToAssets(fTokenBalance));
120
+ }
121
+
122
+ /// @notice Calculates the NAV for the asset state
123
+ /// @dev Returns the value of assets held by the contract in notion
124
+ /// @return The NAV of underlying assets state in notion
125
+ function underlyingAssetNav() public view returns (uint256) {
126
+ address underlyingAssetCached = underlyingAsset;
127
+ return getTokenAmountInNotion(underlyingAssetCached, IERC20(underlyingAssetCached).balanceOf(address(this)));
128
+ }
129
+
130
+ /// @inheritdoc IFluidSupply
131
+ function manualClaim(ClaimParams calldata claimParams) external nonReentrant onlyRole(MERKLE_CLAIMER_ROLE) {
132
+ IFluidMerkleDistributor(merkleDistributor).claim(
133
+ address(this),
134
+ claimParams.cumulativeAmount,
135
+ POSITION_TYPE_LENDING,
136
+ bytes32(uint256(uint160(fToken))),
137
+ claimParams.cycle,
138
+ claimParams.merkleProof,
139
+ claimParams.metadata
140
+ );
141
+ }
142
+
143
+ function _emergencyExit(bytes32 toStateId, uint256 share) internal override {
144
+ if (toStateId == UNDERLYING_ASSET_STATE_ID) {
145
+ _exitFluid(share);
146
+ } else {
147
+ revert StateNotFound(toStateId);
148
+ }
149
+ }
150
+
151
+ function _enterState(bytes32 stateId) internal override {
152
+ if (stateId == FLUID_SUPPLY_STATE_ID) {
153
+ _enterFluid();
154
+ } else {
155
+ revert StateNotFound(stateId);
156
+ }
157
+ }
158
+
159
+ function _exitFromState(bytes32 stateId, uint256 share) internal virtual override {
160
+ if (stateId == FLUID_SUPPLY_STATE_ID) {
161
+ _exitFluid(share);
162
+ } else {
163
+ revert StateNotFound(stateId);
164
+ }
165
+ }
166
+
167
+ function _enterTarget() internal override {
168
+ _enterFluid();
169
+ }
170
+
171
+ function _exitTarget(uint256 share) internal override {
172
+ _exitFluid(share);
173
+ }
174
+
175
+ function _enterFluid() internal {
176
+ address underlyingAssetCached = underlyingAsset;
177
+ address fTokenCached = fToken;
178
+ uint256 underlyingAssetBalance = IERC20(underlyingAssetCached).balanceOf(address(this));
179
+
180
+ if (underlyingAssetBalance == 0) {
181
+ return;
182
+ }
183
+
184
+ IERC20(underlyingAssetCached).safeIncreaseAllowance(fTokenCached, underlyingAssetBalance);
185
+ IFluidToken(fTokenCached).deposit(underlyingAssetBalance, address(this));
186
+ lastAssetsValue = IFluidToken(fTokenCached).convertToAssets(IERC20(fTokenCached).balanceOf(address(this)));
187
+ }
188
+
189
+ function _exitFluid(uint256 share) internal {
190
+ require(share > 0, Errors.ZeroAmount());
191
+ require(share <= MAX_BPS, Errors.IncorrectAmount());
192
+
193
+ address fTokenCached = fToken;
194
+ uint256 fTokenAmountToRedeem = IERC20(fTokenCached).balanceOf(address(this)).mulDiv(share, MAX_BPS);
195
+
196
+ if (fTokenAmountToRedeem == 0) {
197
+ return;
198
+ }
199
+
200
+ IFluidToken(fTokenCached).redeem(fTokenAmountToRedeem, address(this), address(this));
201
+ lastAssetsValue = IFluidToken(fTokenCached).convertToAssets(IERC20(fTokenCached).balanceOf(address(this)));
202
+ }
203
+
204
+ function _calculateAccruedAssetsValue() private view returns (uint256) {
205
+ address fTokenCached = fToken;
206
+ uint256 lastAssetsValueCached = lastAssetsValue;
207
+ uint256 currentAssetsValue = IFluidToken(fTokenCached).convertToAssets(
208
+ IERC20(fTokenCached).balanceOf(address(this))
209
+ );
210
+
211
+ if (currentAssetsValue > lastAssetsValueCached) {
212
+ return currentAssetsValue - lastAssetsValueCached;
213
+ }
214
+
215
+ return 0;
216
+ }
217
+
218
+ function _harvest(bytes32 _stateId, address _treasury, uint256 _feePct) internal override {
219
+ AutomaticHarvestLocalVars memory vars;
220
+ vars.fTokenCached = fToken;
221
+ vars.merkleRewardToken = merkleRewardToken;
222
+
223
+ vars.accruedAssetsValue = _calculateAccruedAssetsValue();
224
+
225
+ if (vars.accruedAssetsValue > 0) {
226
+ vars.accruedFTokenAmount = IFluidToken(vars.fTokenCached).convertToShares(vars.accruedAssetsValue);
227
+ vars.fTokensToTreasury = vars.accruedFTokenAmount.mulDiv(_feePct, MAX_BPS);
228
+ }
229
+
230
+ if (_stateId == FLUID_SUPPLY_STATE_ID) {
231
+ if (IERC20(vars.merkleRewardToken).balanceOf(address(this)) > 0) {
232
+ vars.fTokensAmountBefore = IERC20(vars.fTokenCached).balanceOf(address(this));
233
+ _swapToInputTokens(vars.merkleRewardToken, underlyingAsset, 0, false);
234
+ _enterFluid();
235
+ vars.reinvestFTokenDelta =
236
+ IERC20(vars.fTokenCached).balanceOf(address(this)) - vars.fTokensAmountBefore;
237
+
238
+ if (vars.reinvestFTokenDelta > 0) {
239
+ vars.feeFromReinvest = vars.reinvestFTokenDelta.mulDiv(_feePct, MAX_BPS);
240
+ if (vars.feeFromReinvest > 0) {
241
+ vars.fTokensToTreasury += vars.feeFromReinvest;
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ if (vars.fTokensToTreasury > 0) {
248
+ IERC20(vars.fTokenCached).safeTransfer(_treasury, vars.fTokensToTreasury);
249
+ }
250
+
251
+ lastAssetsValue = IFluidToken(vars.fTokenCached).convertToAssets(
252
+ IERC20(vars.fTokenCached).balanceOf(address(this))
253
+ );
254
+ }
255
+ }