From db6ff34f4f42278cf499511b5401a5f4b0ee8c2b Mon Sep 17 00:00:00 2001 From: Doug Hoyte Date: Wed, 21 Feb 2024 16:15:25 -0500 Subject: [PATCH 1/9] wip: product lines --- src/productLines/BaseProductLine.sol | 92 ++++++++++++++++++++++++++++ src/productLines/Core.sol | 43 +++++++++++++ src/productLines/Edge.sol | 50 +++++++++++++++ src/productLines/Escrow.sol | 43 +++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 src/productLines/BaseProductLine.sol create mode 100644 src/productLines/Core.sol create mode 100644 src/productLines/Edge.sol create mode 100644 src/productLines/Escrow.sol diff --git a/src/productLines/BaseProductLine.sol b/src/productLines/BaseProductLine.sol new file mode 100644 index 00000000..6480f110 --- /dev/null +++ b/src/productLines/BaseProductLine.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import {IERC20, IEVault} from "../EVault/IEVault.sol"; +import {GenericFactory} from "../GenericFactory/GenericFactory.sol"; +import {RevertBytes} from "../EVault/shared/lib/RevertBytes.sol"; + +abstract contract BaseProductLine { + // Constants + + uint256 constant REENTRANCYLOCK__UNLOCKED = 1; + uint256 constant REENTRANCYLOCK__LOCKED = 2; + + address public immutable vaultFactory; + + // State + + uint256 private reentrancyLock; + + mapping(address vault => bool created) public vaultLookup; + address[] public vaultList; + + // Events + + event Genesis(); + event VaultCreated(address indexed vault, address indexed asset, bool upgradeable); + + // Errors + + error E_Reentrancy(); + error E_BadQuery(); + + // Modifiers + + modifier nonReentrant() { + if (reentrancyLock != REENTRANCYLOCK__UNLOCKED) revert E_Reentrancy(); + + reentrancyLock = REENTRANCYLOCK__LOCKED; + _; + reentrancyLock = REENTRANCYLOCK__UNLOCKED; + } + + // Interface + + constructor(address vaultFactory_) { + vaultFactory = vaultFactory_; + + emit Genesis(); + } + + function makeNewVaultInternal(address asset, bool upgradeable) internal returns (IEVault) { + address newVault = GenericFactory(vaultFactory).createProxy(upgradeable, abi.encodePacked(asset)); + + vaultLookup[newVault] = true; + vaultList.push(newVault); + + emit VaultCreated(newVault, asset, upgradeable); + + return IEVault(newVault); + } + + function getTokenName(address asset) internal view returns (string memory) { + // Handle MKR like tokens returning bytes32 + (bool success, bytes memory data) = address(asset).staticcall(abi.encodeWithSelector(IERC20.name.selector)); + if (!success) RevertBytes.revertBytes(data); + return data.length == 32 ? string(data) : abi.decode(data, (string)); + } + + function getTokenSymbol(address asset) internal view returns (string memory) { + // Handle MKR like tokens returning bytes32 + (bool success, bytes memory data) = address(asset).staticcall(abi.encodeWithSelector(IERC20.symbol.selector)); + if (!success) RevertBytes.revertBytes(data); + return data.length == 32 ? string(data) : abi.decode(data, (string)); + } + + // Getters + + function getVaultListLength() external view returns (uint256) { + return vaultList.length; + } + + function getVaultListSlice(uint256 start, uint256 end) external view returns (address[] memory list) { + if (end == type(uint256).max) end = vaultList.length; + if (end < start || end > vaultList.length) revert E_BadQuery(); + + list = new address[](end - start); + for (uint256 i; i < end - start; ++i) { + list[i] = vaultList[start + i]; + } + } +} diff --git a/src/productLines/Core.sol b/src/productLines/Core.sol new file mode 100644 index 00000000..abd43570 --- /dev/null +++ b/src/productLines/Core.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "./BaseProductLine.sol"; + +contract Core is BaseProductLine { + // Constants + + bool public constant UPGRADEABLE = true; + + // State + + address public governor; + + // Errors + + error E_Unauthorized(); + + // Interface + + constructor(address vaultFactory_, address governor_) BaseProductLine(vaultFactory_) { + governor_ = governor; + } + + modifier governorOnly() { + if (msg.sender != governor) revert E_Unauthorized(); + _; + } + + function createVault(address asset) external governorOnly returns (address) { + IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE); + + vault.setName(string.concat("Core vault: ", getTokenName(asset))); + vault.setSymbol(string.concat("e", getTokenSymbol(asset))); + + // FIXME: use different addresses for the following + vault.setFeeReceiver(governor); + vault.setGovernorAdmin(governor); + + return address(vault); + } +} diff --git a/src/productLines/Edge.sol b/src/productLines/Edge.sol new file mode 100644 index 00000000..e608ab14 --- /dev/null +++ b/src/productLines/Edge.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "./BaseProductLine.sol"; + +contract Edge is BaseProductLine { + // Constants + + bool public constant UPGRADEABLE = false; + + // Interface + + constructor(address vaultFactory_) BaseProductLine(vaultFactory_) { + } + + struct Collateral { + address vault; + uint16 ltv; + } + + struct CreateVaultParams { + string name; + // FIXME: IRM (address, or linear kink params and have it deploy a new IRM?) + // FIXME: oracle, unit of account + // FIXME: caps(?), debt socialisation, interestFee + + Collateral[] collaterals; + + address feeReceiver; + } + + function createVault(address asset, CreateVaultParams calldata params) external returns (address) { + IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE); + + vault.setName(string.concat(params.name, " (", getTokenName(asset), " vault)")); + vault.setSymbol(string.concat("e", getTokenSymbol(asset))); + + for (uint i; i < params.collaterals.length; ++i) { + vault.setLTV(params.collaterals[i].vault, params.collaterals[i].ltv, 0); + } + + vault.setFeeReceiver(params.feeReceiver); + + // Renounce governorship + vault.setGovernorAdmin(address(0)); + + return address(vault); + } +} diff --git a/src/productLines/Escrow.sol b/src/productLines/Escrow.sol new file mode 100644 index 00000000..7949454b --- /dev/null +++ b/src/productLines/Escrow.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "./BaseProductLine.sol"; +import "../EVault/shared/Constants.sol"; + +contract Escrow is BaseProductLine { + // Constants + + bool public constant UPGRADEABLE = false; + + // State + + mapping(address asset => address vault) public assetLookup; + + // Errors + + error E_AlreadyCreated(); + + // Interface + + constructor(address vaultFactory_) BaseProductLine(vaultFactory_) { + } + + function createVault(address asset) external returns (address) { + if (assetLookup[asset] != address(0)) revert E_AlreadyCreated(); + + IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE); + + assetLookup[asset] = address(vault); + + vault.setName(string.concat("Escrow vault: ", getTokenName(asset))); + vault.setSymbol(string.concat("e", getTokenSymbol(asset))); + + vault.setMarketPolicy(OP_BORROW | OP_REPAY | OP_WIND | OP_UNWIND | OP_PULL_DEBT | OP_CONVERT_FEES | OP_LIQUIDATE | OP_TOUCH, 0, 0); + + // Renounce governorship + vault.setGovernorAdmin(address(0)); + + return address(vault); + } +} From 32f87b06d6bdbf6238ab71a093affe295d2b0986 Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sat, 23 Mar 2024 20:45:12 +0100 Subject: [PATCH 2/9] update basic product lines --- src/productLines/BaseProductLine.sol | 23 ++++++++++--- src/productLines/Core.sol | 15 ++++++--- src/productLines/Edge.sol | 50 ---------------------------- src/productLines/Escrow.sol | 9 ++--- 4 files changed, 35 insertions(+), 62 deletions(-) delete mode 100644 src/productLines/Edge.sol diff --git a/src/productLines/BaseProductLine.sol b/src/productLines/BaseProductLine.sol index 6480f110..0363e080 100644 --- a/src/productLines/BaseProductLine.sol +++ b/src/productLines/BaseProductLine.sol @@ -2,10 +2,13 @@ pragma solidity ^0.8.0; -import {IERC20, IEVault} from "../EVault/IEVault.sol"; +import {IERC20, IEVault, IGovernance} from "../EVault/IEVault.sol"; import {GenericFactory} from "../GenericFactory/GenericFactory.sol"; import {RevertBytes} from "../EVault/shared/lib/RevertBytes.sol"; +import "../EVault/shared/Constants.sol"; + +/// @notice Base contract for product line contracts, which deploy pre-configured EVaults through a GenericFactory abstract contract BaseProductLine { // Constants @@ -13,6 +16,7 @@ abstract contract BaseProductLine { uint256 constant REENTRANCYLOCK__LOCKED = 2; address public immutable vaultFactory; + address public immutable evc; // State @@ -43,18 +47,24 @@ abstract contract BaseProductLine { // Interface - constructor(address vaultFactory_) { + constructor(address vaultFactory_, address evc_) { vaultFactory = vaultFactory_; + evc = evc_; emit Genesis(); } - function makeNewVaultInternal(address asset, bool upgradeable) internal returns (IEVault) { - address newVault = GenericFactory(vaultFactory).createProxy(upgradeable, abi.encodePacked(asset)); + function makeNewVaultInternal(address asset, bool upgradeable, address oracle, address unitOfAccount) internal returns (IEVault) { + address newVault = GenericFactory(vaultFactory).createProxy(upgradeable, abi.encodePacked(asset, oracle, unitOfAccount)); vaultLookup[newVault] = true; vaultList.push(newVault); + if (isEVCCompatible(asset)) { + uint32 flags = IEVault(newVault).configFlags(); + IEVault(newVault).setConfigFlags(flags | CFG_EVC_COMPATIBLE_ASSET); + } + emit VaultCreated(newVault, asset, upgradeable); return IEVault(newVault); @@ -89,4 +99,9 @@ abstract contract BaseProductLine { list[i] = vaultList[start + i]; } } + + function isEVCCompatible(address asset) private view returns (bool) { + (bool success, bytes memory data) = asset.staticcall(abi.encodeCall(IGovernance.EVC, ())); + return success && data.length >= 32 && abi.decode(data, (address)) == address(evc); + } } diff --git a/src/productLines/Core.sol b/src/productLines/Core.sol index abd43570..0b9b142b 100644 --- a/src/productLines/Core.sol +++ b/src/productLines/Core.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "./BaseProductLine.sol"; +/// @notice Contract deploying EVaults, forming the `Core` product line, which are upgradeable and fully governed. contract Core is BaseProductLine { // Constants @@ -12,6 +13,9 @@ contract Core is BaseProductLine { // State address public governor; + address public feeReceiver; + address public oracle; + address public unitOfAccount; // Errors @@ -19,8 +23,12 @@ contract Core is BaseProductLine { // Interface - constructor(address vaultFactory_, address governor_) BaseProductLine(vaultFactory_) { - governor_ = governor; + constructor(address vaultFactory_, address evc_, address governor_, address feeReceiver_, address oracle_, address unitOfAccount_) BaseProductLine(vaultFactory_, evc_) { + governor = governor_; + feeReceiver = feeReceiver_; + + oracle = oracle_; + unitOfAccount = unitOfAccount_; } modifier governorOnly() { @@ -29,12 +37,11 @@ contract Core is BaseProductLine { } function createVault(address asset) external governorOnly returns (address) { - IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE); + IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE, oracle, unitOfAccount); vault.setName(string.concat("Core vault: ", getTokenName(asset))); vault.setSymbol(string.concat("e", getTokenSymbol(asset))); - // FIXME: use different addresses for the following vault.setFeeReceiver(governor); vault.setGovernorAdmin(governor); diff --git a/src/productLines/Edge.sol b/src/productLines/Edge.sol deleted file mode 100644 index e608ab14..00000000 --- a/src/productLines/Edge.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.8.0; - -import "./BaseProductLine.sol"; - -contract Edge is BaseProductLine { - // Constants - - bool public constant UPGRADEABLE = false; - - // Interface - - constructor(address vaultFactory_) BaseProductLine(vaultFactory_) { - } - - struct Collateral { - address vault; - uint16 ltv; - } - - struct CreateVaultParams { - string name; - // FIXME: IRM (address, or linear kink params and have it deploy a new IRM?) - // FIXME: oracle, unit of account - // FIXME: caps(?), debt socialisation, interestFee - - Collateral[] collaterals; - - address feeReceiver; - } - - function createVault(address asset, CreateVaultParams calldata params) external returns (address) { - IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE); - - vault.setName(string.concat(params.name, " (", getTokenName(asset), " vault)")); - vault.setSymbol(string.concat("e", getTokenSymbol(asset))); - - for (uint i; i < params.collaterals.length; ++i) { - vault.setLTV(params.collaterals[i].vault, params.collaterals[i].ltv, 0); - } - - vault.setFeeReceiver(params.feeReceiver); - - // Renounce governorship - vault.setGovernorAdmin(address(0)); - - return address(vault); - } -} diff --git a/src/productLines/Escrow.sol b/src/productLines/Escrow.sol index 7949454b..200e150d 100644 --- a/src/productLines/Escrow.sol +++ b/src/productLines/Escrow.sol @@ -5,6 +5,8 @@ pragma solidity ^0.8.0; import "./BaseProductLine.sol"; import "../EVault/shared/Constants.sol"; +/// @notice Contract deploying EVaults, forming the `Escrow` product line, which are non-upgradeable +/// non-governed, don't allow borrowing and only allow one instance per asset. contract Escrow is BaseProductLine { // Constants @@ -20,20 +22,19 @@ contract Escrow is BaseProductLine { // Interface - constructor(address vaultFactory_) BaseProductLine(vaultFactory_) { - } + constructor(address vaultFactory_, address evc_) BaseProductLine(vaultFactory_, evc_) {} function createVault(address asset) external returns (address) { if (assetLookup[asset] != address(0)) revert E_AlreadyCreated(); - IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE); + IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE, address(0), address(0)); assetLookup[asset] = address(vault); vault.setName(string.concat("Escrow vault: ", getTokenName(asset))); vault.setSymbol(string.concat("e", getTokenSymbol(asset))); - vault.setMarketPolicy(OP_BORROW | OP_REPAY | OP_WIND | OP_UNWIND | OP_PULL_DEBT | OP_CONVERT_FEES | OP_LIQUIDATE | OP_TOUCH, 0, 0); + vault.setDisabledOps(OP_BORROW | OP_REPAY | OP_LOOP | OP_DELOOP | OP_PULL_DEBT | OP_CONVERT_FEES | OP_LIQUIDATE | OP_TOUCH | OP_ACCRUE_INTEREST); // Renounce governorship vault.setGovernorAdmin(address(0)); From 77c6a69641ac1287687107c2df59083e4894bf03 Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sat, 23 Mar 2024 21:14:08 +0100 Subject: [PATCH 3/9] use product lines to set up tests --- src/productLines/Core.sol | 2 +- test/unit/evault/EVaultTestBase.t.sol | 15 ++++++++++++--- test/unit/evault/modules/Token/views.t.sol | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/productLines/Core.sol b/src/productLines/Core.sol index 0b9b142b..57fa53dc 100644 --- a/src/productLines/Core.sol +++ b/src/productLines/Core.sol @@ -42,7 +42,7 @@ contract Core is BaseProductLine { vault.setName(string.concat("Core vault: ", getTokenName(asset))); vault.setSymbol(string.concat("e", getTokenSymbol(asset))); - vault.setFeeReceiver(governor); + vault.setFeeReceiver(feeReceiver); vault.setGovernorAdmin(governor); return address(vault); diff --git a/test/unit/evault/EVaultTestBase.t.sol b/test/unit/evault/EVaultTestBase.t.sol index 7bf5fe3f..c1a697c0 100644 --- a/test/unit/evault/EVaultTestBase.t.sol +++ b/test/unit/evault/EVaultTestBase.t.sol @@ -24,6 +24,9 @@ import {IEVault, IERC20} from "src/EVault/IEVault.sol"; import {TypesLib} from "src/EVault/shared/types/Types.sol"; import {Base} from "src/EVault/shared/Base.sol"; +import {Core} from "src/ProductLines/Core.sol"; +import {Escrow} from "src/ProductLines/Escrow.sol"; + import {EthereumVaultConnector} from "ethereum-vault-connector/EthereumVaultConnector.sol"; import {TestERC20} from "../../mocks/TestERC20.sol"; @@ -46,6 +49,9 @@ contract EVaultTestBase is AssertionsCustomTypes, Test, DeployPermit2 { address permit2; GenericFactory public factory; + Core public coreProductLine; + Escrow public escrowProductLine; + Base.Integrations integrations; Dispatch.DeployedModules modules; @@ -102,14 +108,17 @@ contract EVaultTestBase is AssertionsCustomTypes, Test, DeployPermit2 { vm.prank(admin); factory.setImplementation(evaultImpl); + coreProductLine = new Core(address(factory), address(evc), address(this), feeReceiver, address(oracle), unitOfAccount); + escrowProductLine = new Escrow(address(factory), address(evc)); + assetTST = new TestERC20("Test Token", "TST", 18, false); assetTST2 = new TestERC20("Test Token 2", "TST2", 18, false); - eTST = IEVault(factory.createProxy(true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount))); + eTST = IEVault(coreProductLine.createVault(address(assetTST))); eTST.setInterestRateModel(address(new IRMTestDefault())); - eTST2 = IEVault(factory.createProxy(true, abi.encodePacked(address(assetTST2), address(oracle), unitOfAccount))); - eTST2.setInterestRateModel(address(new IRMTestDefault())); + eTST2 = IEVault(coreProductLine.createVault(address(assetTST2))); + eTST.setInterestRateModel(address(new IRMTestDefault())); } uint32 internal constant SYNTH_VAULT_DISABLED_OPS = OP_MINT | OP_REDEEM | OP_SKIM | OP_LOOP | OP_DELOOP; diff --git a/test/unit/evault/modules/Token/views.t.sol b/test/unit/evault/modules/Token/views.t.sol index 6e59a9e2..2d278211 100644 --- a/test/unit/evault/modules/Token/views.t.sol +++ b/test/unit/evault/modules/Token/views.t.sol @@ -6,8 +6,8 @@ import "../../EVaultTestBase.t.sol"; contract ERC20Test_views is EVaultTestBase { function test_basicViews() public view { - assertEq(eTST.name(), "Unnamed Euler Vault"); - assertEq(eTST.symbol(), "UNKNOWN"); + assertEq(eTST.name(), "Core vault: Test Token"); + assertEq(eTST.symbol(), "eTST"); assertEq(eTST.decimals(), assetTST.decimals()); } } From 715e0986fc6b4ea6188f6b375084b0bcb200a2c0 Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sat, 23 Mar 2024 21:49:02 +0100 Subject: [PATCH 4/9] fixes, basic tests --- src/productLines/BaseProductLine.sol | 8 ++++++-- src/productLines/Core.sol | 15 ++++++++------- src/productLines/Escrow.sol | 5 ++++- test/unit/evault/EVaultTestBase.t.sol | 6 +++--- test/unit/productLines/views.t.sol | 19 +++++++++++++++++++ 5 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 test/unit/productLines/views.t.sol diff --git a/src/productLines/BaseProductLine.sol b/src/productLines/BaseProductLine.sol index 0363e080..0a3795d3 100644 --- a/src/productLines/BaseProductLine.sol +++ b/src/productLines/BaseProductLine.sol @@ -54,8 +54,12 @@ abstract contract BaseProductLine { emit Genesis(); } - function makeNewVaultInternal(address asset, bool upgradeable, address oracle, address unitOfAccount) internal returns (IEVault) { - address newVault = GenericFactory(vaultFactory).createProxy(upgradeable, abi.encodePacked(asset, oracle, unitOfAccount)); + function makeNewVaultInternal(address asset, bool upgradeable, address oracle, address unitOfAccount) + internal + returns (IEVault) + { + address newVault = + GenericFactory(vaultFactory).createProxy(upgradeable, abi.encodePacked(asset, oracle, unitOfAccount)); vaultLookup[newVault] = true; vaultList.push(newVault); diff --git a/src/productLines/Core.sol b/src/productLines/Core.sol index 57fa53dc..57f11b97 100644 --- a/src/productLines/Core.sol +++ b/src/productLines/Core.sol @@ -14,8 +14,6 @@ contract Core is BaseProductLine { address public governor; address public feeReceiver; - address public oracle; - address public unitOfAccount; // Errors @@ -23,12 +21,11 @@ contract Core is BaseProductLine { // Interface - constructor(address vaultFactory_, address evc_, address governor_, address feeReceiver_, address oracle_, address unitOfAccount_) BaseProductLine(vaultFactory_, evc_) { + constructor(address vaultFactory_, address evc_, address governor_, address feeReceiver_) + BaseProductLine(vaultFactory_, evc_) + { governor = governor_; feeReceiver = feeReceiver_; - - oracle = oracle_; - unitOfAccount = unitOfAccount_; } modifier governorOnly() { @@ -36,7 +33,11 @@ contract Core is BaseProductLine { _; } - function createVault(address asset) external governorOnly returns (address) { + function createVault(address asset, address oracle, address unitOfAccount) + external + governorOnly + returns (address) + { IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE, oracle, unitOfAccount); vault.setName(string.concat("Core vault: ", getTokenName(asset))); diff --git a/src/productLines/Escrow.sol b/src/productLines/Escrow.sol index 200e150d..0cdbd3e8 100644 --- a/src/productLines/Escrow.sol +++ b/src/productLines/Escrow.sol @@ -34,7 +34,10 @@ contract Escrow is BaseProductLine { vault.setName(string.concat("Escrow vault: ", getTokenName(asset))); vault.setSymbol(string.concat("e", getTokenSymbol(asset))); - vault.setDisabledOps(OP_BORROW | OP_REPAY | OP_LOOP | OP_DELOOP | OP_PULL_DEBT | OP_CONVERT_FEES | OP_LIQUIDATE | OP_TOUCH | OP_ACCRUE_INTEREST); + vault.setDisabledOps( + OP_BORROW | OP_REPAY | OP_LOOP | OP_DELOOP | OP_PULL_DEBT | OP_CONVERT_FEES | OP_LIQUIDATE | OP_TOUCH + | OP_ACCRUE_INTEREST + ); // Renounce governorship vault.setGovernorAdmin(address(0)); diff --git a/test/unit/evault/EVaultTestBase.t.sol b/test/unit/evault/EVaultTestBase.t.sol index c1a697c0..a3879228 100644 --- a/test/unit/evault/EVaultTestBase.t.sol +++ b/test/unit/evault/EVaultTestBase.t.sol @@ -108,16 +108,16 @@ contract EVaultTestBase is AssertionsCustomTypes, Test, DeployPermit2 { vm.prank(admin); factory.setImplementation(evaultImpl); - coreProductLine = new Core(address(factory), address(evc), address(this), feeReceiver, address(oracle), unitOfAccount); + coreProductLine = new Core(address(factory), address(evc), address(this), feeReceiver); escrowProductLine = new Escrow(address(factory), address(evc)); assetTST = new TestERC20("Test Token", "TST", 18, false); assetTST2 = new TestERC20("Test Token 2", "TST2", 18, false); - eTST = IEVault(coreProductLine.createVault(address(assetTST))); + eTST = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount)); eTST.setInterestRateModel(address(new IRMTestDefault())); - eTST2 = IEVault(coreProductLine.createVault(address(assetTST2))); + eTST2 = IEVault(coreProductLine.createVault(address(assetTST2), address(oracle), unitOfAccount)); eTST.setInterestRateModel(address(new IRMTestDefault())); } diff --git a/test/unit/productLines/views.t.sol b/test/unit/productLines/views.t.sol new file mode 100644 index 00000000..fece05a9 --- /dev/null +++ b/test/unit/productLines/views.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "../EVault/EVaultTestBase.t.sol"; + +contract ProductLine_Core is EVaultTestBase { + function test_Core_basicViews() public view { + assertEq(eTST.unitOfAccount(), unitOfAccount); + assertEq(eTST.oracle(), address(oracle)); + assertEq(eTST.feeReceiver(), feeReceiver); + } + + function test_Core_EVCCompatibility() public { + assertEq(eTST.configFlags(), 0); + IEVault nested = IEVault(coreProductLine.createVault(address(eTST), address(oracle), unitOfAccount)); + assertEq(nested.configFlags(), CFG_EVC_COMPATIBLE_ASSET); + } +} From 073e498d3741423012e661e08de3d88bb87e7d8a Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sat, 23 Mar 2024 22:39:20 +0100 Subject: [PATCH 5/9] fix folders --- src/{productLines => ProductLines}/BaseProductLine.sol | 0 src/{productLines => ProductLines}/Core.sol | 0 src/{productLines => ProductLines}/Escrow.sol | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/{productLines => ProductLines}/BaseProductLine.sol (100%) rename src/{productLines => ProductLines}/Core.sol (100%) rename src/{productLines => ProductLines}/Escrow.sol (100%) diff --git a/src/productLines/BaseProductLine.sol b/src/ProductLines/BaseProductLine.sol similarity index 100% rename from src/productLines/BaseProductLine.sol rename to src/ProductLines/BaseProductLine.sol diff --git a/src/productLines/Core.sol b/src/ProductLines/Core.sol similarity index 100% rename from src/productLines/Core.sol rename to src/ProductLines/Core.sol diff --git a/src/productLines/Escrow.sol b/src/ProductLines/Escrow.sol similarity index 100% rename from src/productLines/Escrow.sol rename to src/ProductLines/Escrow.sol From 34d74cece72680aef7059adb4cd5d73ef900b175 Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sat, 23 Mar 2024 22:47:47 +0100 Subject: [PATCH 6/9] fix path --- test/unit/productLines/views.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/productLines/views.t.sol b/test/unit/productLines/views.t.sol index fece05a9..8a5de531 100644 --- a/test/unit/productLines/views.t.sol +++ b/test/unit/productLines/views.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import "../EVault/EVaultTestBase.t.sol"; +import "../evault/EVaultTestBase.t.sol"; contract ProductLine_Core is EVaultTestBase { function test_Core_basicViews() public view { From 2c243193bc90361729851ed55c8ebb5eebdec038 Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sun, 24 Mar 2024 08:55:51 +0100 Subject: [PATCH 7/9] review fixes --- src/ProductLines/BaseProductLine.sol | 2 +- src/ProductLines/Core.sol | 2 +- src/ProductLines/Escrow.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ProductLines/BaseProductLine.sol b/src/ProductLines/BaseProductLine.sol index 0a3795d3..6b9f2b8a 100644 --- a/src/ProductLines/BaseProductLine.sol +++ b/src/ProductLines/BaseProductLine.sol @@ -54,7 +54,7 @@ abstract contract BaseProductLine { emit Genesis(); } - function makeNewVaultInternal(address asset, bool upgradeable, address oracle, address unitOfAccount) + function makeNewVaultInternal(bool upgradeable, address asset, address oracle, address unitOfAccount) internal returns (IEVault) { diff --git a/src/ProductLines/Core.sol b/src/ProductLines/Core.sol index 57f11b97..c73745a4 100644 --- a/src/ProductLines/Core.sol +++ b/src/ProductLines/Core.sol @@ -38,7 +38,7 @@ contract Core is BaseProductLine { governorOnly returns (address) { - IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE, oracle, unitOfAccount); + IEVault vault = makeNewVaultInternal(UPGRADEABLE, asset, oracle, unitOfAccount); vault.setName(string.concat("Core vault: ", getTokenName(asset))); vault.setSymbol(string.concat("e", getTokenSymbol(asset))); diff --git a/src/ProductLines/Escrow.sol b/src/ProductLines/Escrow.sol index 0cdbd3e8..00d8f872 100644 --- a/src/ProductLines/Escrow.sol +++ b/src/ProductLines/Escrow.sol @@ -27,7 +27,7 @@ contract Escrow is BaseProductLine { function createVault(address asset) external returns (address) { if (assetLookup[asset] != address(0)) revert E_AlreadyCreated(); - IEVault vault = makeNewVaultInternal(asset, UPGRADEABLE, address(0), address(0)); + IEVault vault = makeNewVaultInternal(UPGRADEABLE, asset, address(0), address(0)); assetLookup[asset] = address(vault); From d2aede14ac851da6049d0eb2b024be88fc286acc Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sun, 24 Mar 2024 10:25:19 +0100 Subject: [PATCH 8/9] add tests --- src/ProductLines/BaseProductLine.sol | 28 ++++++++--------- .../unit/productLines/productLines.base.t.sol | 15 ++++++++++ .../{views.t.sol => productLines.core.t.sol} | 7 +++-- .../productLines/productLines.escrow.t.sol | 30 +++++++++++++++++++ 4 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 test/unit/productLines/productLines.base.t.sol rename test/unit/productLines/{views.t.sol => productLines.core.t.sol} (68%) create mode 100644 test/unit/productLines/productLines.escrow.t.sol diff --git a/src/ProductLines/BaseProductLine.sol b/src/ProductLines/BaseProductLine.sol index 6b9f2b8a..575d3360 100644 --- a/src/ProductLines/BaseProductLine.sol +++ b/src/ProductLines/BaseProductLine.sol @@ -74,20 +74,6 @@ abstract contract BaseProductLine { return IEVault(newVault); } - function getTokenName(address asset) internal view returns (string memory) { - // Handle MKR like tokens returning bytes32 - (bool success, bytes memory data) = address(asset).staticcall(abi.encodeWithSelector(IERC20.name.selector)); - if (!success) RevertBytes.revertBytes(data); - return data.length == 32 ? string(data) : abi.decode(data, (string)); - } - - function getTokenSymbol(address asset) internal view returns (string memory) { - // Handle MKR like tokens returning bytes32 - (bool success, bytes memory data) = address(asset).staticcall(abi.encodeWithSelector(IERC20.symbol.selector)); - if (!success) RevertBytes.revertBytes(data); - return data.length == 32 ? string(data) : abi.decode(data, (string)); - } - // Getters function getVaultListLength() external view returns (uint256) { @@ -104,6 +90,20 @@ abstract contract BaseProductLine { } } + function getTokenName(address asset) internal view returns (string memory) { + // Handle MKR like tokens returning bytes32 + (bool success, bytes memory data) = address(asset).staticcall(abi.encodeWithSelector(IERC20.name.selector)); + if (!success) RevertBytes.revertBytes(data); + return data.length == 32 ? string(data) : abi.decode(data, (string)); + } + + function getTokenSymbol(address asset) internal view returns (string memory) { + // Handle MKR like tokens returning bytes32 + (bool success, bytes memory data) = address(asset).staticcall(abi.encodeWithSelector(IERC20.symbol.selector)); + if (!success) RevertBytes.revertBytes(data); + return data.length == 32 ? string(data) : abi.decode(data, (string)); + } + function isEVCCompatible(address asset) private view returns (bool) { (bool success, bytes memory data) = asset.staticcall(abi.encodeCall(IGovernance.EVC, ())); return success && data.length >= 32 && abi.decode(data, (address)) == address(evc); diff --git a/test/unit/productLines/productLines.base.t.sol b/test/unit/productLines/productLines.base.t.sol new file mode 100644 index 00000000..824675ea --- /dev/null +++ b/test/unit/productLines/productLines.base.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "../evault/EVaultTestBase.t.sol"; + +contract ProductLine_Base is EVaultTestBase { + function test_ProductLine_Base_lookup() public view { + assertEq(coreProductLine.vaultLookup(address(eTST)), true); + assertEq(coreProductLine.vaultLookup(vm.addr(100)), false); + assertEq(coreProductLine.getVaultListLength(), 2); + assertEq(coreProductLine.getVaultListSlice(0, type(uint).max)[0], address(eTST)); + assertEq(coreProductLine.getVaultListSlice(0, type(uint).max)[1], address(eTST2)); + } +} diff --git a/test/unit/productLines/views.t.sol b/test/unit/productLines/productLines.core.t.sol similarity index 68% rename from test/unit/productLines/views.t.sol rename to test/unit/productLines/productLines.core.t.sol index 8a5de531..4926ef1c 100644 --- a/test/unit/productLines/views.t.sol +++ b/test/unit/productLines/productLines.core.t.sol @@ -5,13 +5,16 @@ pragma solidity ^0.8.0; import "../evault/EVaultTestBase.t.sol"; contract ProductLine_Core is EVaultTestBase { - function test_Core_basicViews() public view { + function test_ProductLine_Core_basicViews() public view { + assertEq(factory.getProxyConfig(address(eTST)).upgradeable, true); + assertEq(eTST.unitOfAccount(), unitOfAccount); assertEq(eTST.oracle(), address(oracle)); assertEq(eTST.feeReceiver(), feeReceiver); + assertEq(eTST.governorAdmin(), address(this)); } - function test_Core_EVCCompatibility() public { + function test_ProductLine_Core_EVCCompatibility() public { assertEq(eTST.configFlags(), 0); IEVault nested = IEVault(coreProductLine.createVault(address(eTST), address(oracle), unitOfAccount)); assertEq(nested.configFlags(), CFG_EVC_COMPATIBLE_ASSET); diff --git a/test/unit/productLines/productLines.escrow.t.sol b/test/unit/productLines/productLines.escrow.t.sol new file mode 100644 index 00000000..09429a30 --- /dev/null +++ b/test/unit/productLines/productLines.escrow.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "src/ProductLines/Escrow.sol"; +import "../evault/EVaultTestBase.t.sol"; + +contract ProductLine_Escrow is EVaultTestBase { + uint32 constant ESCROW_DISABLED_OPS = OP_BORROW | OP_REPAY | OP_LOOP | OP_DELOOP | OP_PULL_DEBT | OP_CONVERT_FEES | OP_LIQUIDATE | OP_TOUCH + | OP_ACCRUE_INTEREST; + + function test_ProductLine_Escrow_basicViews() public { + IEVault escrowTST = IEVault(escrowProductLine.createVault(address(assetTST))); + + assertEq(factory.getProxyConfig(address(escrowTST)).upgradeable, false); + + assertEq(escrowTST.name(), "Escrow vault: Test Token"); + assertEq(escrowTST.symbol(), "eTST"); + assertEq(escrowTST.unitOfAccount(), address(0)); + assertEq(escrowTST.oracle(), address(0)); + assertEq(escrowTST.disabledOps(), ESCROW_DISABLED_OPS); + } + + function test_ProductLine_Escrow_RevertWhenAlreadyCreated() public { + escrowProductLine.createVault(address(assetTST)); + + vm.expectRevert(Escrow.E_AlreadyCreated.selector); + escrowProductLine.createVault(address(assetTST)); + } +} From a2f23b92427180cb71128e032cabaddca718b830 Mon Sep 17 00:00:00 2001 From: dglowinski Date: Sun, 24 Mar 2024 15:29:25 +0100 Subject: [PATCH 9/9] formatting --- test/unit/productLines/productLines.base.t.sol | 4 ++-- test/unit/productLines/productLines.escrow.t.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/productLines/productLines.base.t.sol b/test/unit/productLines/productLines.base.t.sol index 824675ea..21a19d52 100644 --- a/test/unit/productLines/productLines.base.t.sol +++ b/test/unit/productLines/productLines.base.t.sol @@ -9,7 +9,7 @@ contract ProductLine_Base is EVaultTestBase { assertEq(coreProductLine.vaultLookup(address(eTST)), true); assertEq(coreProductLine.vaultLookup(vm.addr(100)), false); assertEq(coreProductLine.getVaultListLength(), 2); - assertEq(coreProductLine.getVaultListSlice(0, type(uint).max)[0], address(eTST)); - assertEq(coreProductLine.getVaultListSlice(0, type(uint).max)[1], address(eTST2)); + assertEq(coreProductLine.getVaultListSlice(0, type(uint256).max)[0], address(eTST)); + assertEq(coreProductLine.getVaultListSlice(0, type(uint256).max)[1], address(eTST2)); } } diff --git a/test/unit/productLines/productLines.escrow.t.sol b/test/unit/productLines/productLines.escrow.t.sol index 09429a30..7abf6ecf 100644 --- a/test/unit/productLines/productLines.escrow.t.sol +++ b/test/unit/productLines/productLines.escrow.t.sol @@ -6,8 +6,8 @@ import "src/ProductLines/Escrow.sol"; import "../evault/EVaultTestBase.t.sol"; contract ProductLine_Escrow is EVaultTestBase { - uint32 constant ESCROW_DISABLED_OPS = OP_BORROW | OP_REPAY | OP_LOOP | OP_DELOOP | OP_PULL_DEBT | OP_CONVERT_FEES | OP_LIQUIDATE | OP_TOUCH - | OP_ACCRUE_INTEREST; + uint32 constant ESCROW_DISABLED_OPS = OP_BORROW | OP_REPAY | OP_LOOP | OP_DELOOP | OP_PULL_DEBT | OP_CONVERT_FEES + | OP_LIQUIDATE | OP_TOUCH | OP_ACCRUE_INTEREST; function test_ProductLine_Escrow_basicViews() public { IEVault escrowTST = IEVault(escrowProductLine.createVault(address(assetTST)));