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
93 changes: 93 additions & 0 deletions contracts/ERC4626InvestStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IInvestStrategy} from "./interfaces/IInvestStrategy.sol";
import {InvestStrategyClient} from "./InvestStrategyClient.sol";

/**
* @title AaveV3InvestStrategy
*
* @dev Strategy that invests/deinvests into a 4626 vault
*
* @custom:security-contact [email protected]
* @author Ensuro
*/
contract ERC4626InvestStrategy is IInvestStrategy {
address private immutable __self = address(this);
bytes32 public immutable storageSlot = InvestStrategyClient.makeStorageSlot(this);

IERC4626 internal immutable _vault;
IERC20 internal immutable _asset;

error CanBeCalledOnlyThroughDelegateCall();
error CannotDisconnectWithAssets();
error NoExtraDataAllowed();

modifier onlyDelegCall() {
if (address(this) == __self) revert CanBeCalledOnlyThroughDelegateCall();
_;
}

constructor(IERC4626 vault_) {
_vault = vault_;
_asset = IERC20(vault_.asset());
}

/// @inheritdoc IInvestStrategy
function connect(bytes memory initData) external virtual override onlyDelegCall {
if (initData.length != 0) revert NoExtraDataAllowed();
}

/// @inheritdoc IInvestStrategy
function disconnect(bool force) external virtual override onlyDelegCall {
// Here I check _vault.balanceOf() instead of totalAssets(). In an extreme cases, when the vault lost all its
// value these can differ, but on those cases I think it's safer to block the disconnection unless forced
if (!force && _vault.balanceOf(address(this)) != 0) revert CannotDisconnectWithAssets();
}

/// @inheritdoc IInvestStrategy
function maxWithdraw(address contract_) public view virtual override returns (uint256) {
return _vault.maxWithdraw(contract_);
}

/// @inheritdoc IInvestStrategy
function maxDeposit(address contract_) public view virtual override returns (uint256) {
return _vault.maxDeposit(contract_);
}

/// @inheritdoc IInvestStrategy
function asset(address) public view virtual override returns (address) {
return address(_asset);
}

/**
* @dev Returns the ERC4626 where this strategy invests the funds
*/
function investVault() public view returns (IERC4626) {
return _vault;
}

/// @inheritdoc IInvestStrategy
function totalAssets(address contract_) public view virtual override returns (uint256 assets) {
return _vault.convertToAssets(_vault.balanceOf(contract_));
}

/// @inheritdoc IInvestStrategy
function withdraw(uint256 assets) external virtual override onlyDelegCall {
_vault.withdraw(assets, address(this), address(this));
}

/// @inheritdoc IInvestStrategy
function deposit(uint256 assets) external virtual override onlyDelegCall {
_asset.approve(address(_vault), assets);
_vault.deposit(assets, address(this));
}

/// @inheritdoc IInvestStrategy
function forwardEntryPoint(uint8, bytes memory) external view onlyDelegCall returns (bytes memory) {
// solhint-disable-next-line gas-custom-errors,reason-string
revert();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En este caso no es mejor implementar un custom error NotImplemented() en lugar de un revert a secas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En varios lugares lo tenemos así, como un revert sin motivo. NotImplemented no aportaría demasiado

}
}
1 change: 1 addition & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module.exports = {
dependencyCompiler: {
paths: [
"@ensuro/utils/contracts/TestCurrency.sol",
"@ensuro/utils/contracts/TestERC4626.sol",
"@ensuro/swaplibrary/contracts/mocks/SwapRouterMock.sol",
"@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol",
"@openzeppelin/contracts/access/manager/AccessManager.sol",
Expand Down
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"solhint-plugin-prettier": "0.1.0"
},
"dependencies": {
"@ensuro/utils": "^0.1.1",
"@ensuro/utils": "^0.2.6",
"@ensuro/swaplibrary": "1.0.0",
"@openzeppelin/contracts": "^5.1.0",
"@openzeppelin/contracts-upgradeable": "^5.1.0",
Expand Down
2 changes: 1 addition & 1 deletion test/test-compound-v3-vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ variants.forEach((variant) => {
expect(evt).not.equal(null);

expect(evt.args.token).to.equal(ADDRESSES.COMP);
expect(evt.args.rewards).to.equal(_W("0.126432"));
expect(evt.args.rewards).to.closeTo(_W("0.126432"), _W("0.00001"));
expect(evt.args.receivedInAsset).to.equal(_A("10.684546"));

await expect(tx).to.emit(currency, "Transfer").withArgs(vault, ADDRESSES.cUSDCv3, _A("10.684546"));
Expand Down
Loading
Loading